Better test runner (#3922)
8
.gitignore
vendored
@ -6,11 +6,8 @@ desktop.ini
|
||||
.DS_Store
|
||||
|
||||
# Tests and benchmarks
|
||||
tests/png
|
||||
tests/pdf
|
||||
tests/svg
|
||||
tests/target
|
||||
tests/typ/**/*.pdf
|
||||
tests/store
|
||||
tests/suite/**/*.pdf
|
||||
tests/fuzz/target
|
||||
tests/fuzz/corpus
|
||||
tests/fuzz/artifacts
|
||||
@ -23,6 +20,7 @@ tarpaulin-report.html
|
||||
|
||||
# Node
|
||||
node_modules
|
||||
tools/test-helper/dist
|
||||
package-lock.json
|
||||
|
||||
# Nix
|
||||
|
5
Cargo.lock
generated
@ -2675,8 +2675,11 @@ dependencies = [
|
||||
"ecow",
|
||||
"if_chain",
|
||||
"log",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"typst",
|
||||
"typst-assets",
|
||||
"typst-dev-assets",
|
||||
"unscanny",
|
||||
]
|
||||
|
||||
@ -2773,13 +2776,13 @@ dependencies = [
|
||||
"ecow",
|
||||
"once_cell",
|
||||
"oxipng",
|
||||
"parking_lot",
|
||||
"rayon",
|
||||
"tiny-skia",
|
||||
"ttf-parser",
|
||||
"typst",
|
||||
"typst-assets",
|
||||
"typst-dev-assets",
|
||||
"typst-ide",
|
||||
"typst-pdf",
|
||||
"typst-render",
|
||||
"typst-svg",
|
||||
|
@ -226,7 +226,7 @@ struct FileSlot {
|
||||
}
|
||||
|
||||
impl FileSlot {
|
||||
/// Create a new path slot.
|
||||
/// Create a new file slot.
|
||||
fn new(id: FileId) -> Self {
|
||||
Self { id, file: SlotCell::new(), source: SlotCell::new() }
|
||||
}
|
||||
|
@ -21,5 +21,10 @@ log = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
unscanny = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
typst-assets = { workspace = true }
|
||||
typst-dev-assets = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
@ -1403,3 +1403,34 @@ impl<'a> CompletionContext<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use typst::eval::Tracer;
|
||||
|
||||
use super::autocomplete;
|
||||
use crate::tests::TestWorld;
|
||||
|
||||
#[track_caller]
|
||||
fn test(text: &str, cursor: usize, contains: &[&str], excludes: &[&str]) {
|
||||
let world = TestWorld::new(text);
|
||||
let doc = typst::compile(&world, &mut Tracer::new()).ok();
|
||||
let (_, completions) =
|
||||
autocomplete(&world, doc.as_ref(), &world.main, cursor, true)
|
||||
.unwrap_or_default();
|
||||
|
||||
let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
|
||||
for item in contains {
|
||||
assert!(labels.contains(item), "{item:?} was not contained in {labels:?}");
|
||||
}
|
||||
for item in excludes {
|
||||
assert!(!labels.contains(item), "{item:?} was not excluded in {labels:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_autocomplete() {
|
||||
test("#i", 2, &["int", "if conditional"], &["foo"]);
|
||||
test("#().", 4, &["insert", "remove", "len", "all"], &["foo"]);
|
||||
}
|
||||
}
|
||||
|
@ -90,3 +90,88 @@ fn summarize_font_family<'a>(variants: impl Iterator<Item = &'a FontInfo>) -> Ec
|
||||
|
||||
detail
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use comemo::Prehashed;
|
||||
use once_cell::sync::Lazy;
|
||||
use typst::diag::{FileError, FileResult};
|
||||
use typst::foundations::{Bytes, Datetime};
|
||||
use typst::syntax::{FileId, Source};
|
||||
use typst::text::{Font, FontBook};
|
||||
use typst::{Library, World};
|
||||
|
||||
/// A world for IDE testing.
|
||||
pub struct TestWorld {
|
||||
pub main: Source,
|
||||
base: &'static TestBase,
|
||||
}
|
||||
|
||||
impl TestWorld {
|
||||
/// Create a new world for a single test.
|
||||
///
|
||||
/// This is cheap because the shared base for all test runs is lazily
|
||||
/// initialized just once.
|
||||
pub fn new(text: &str) -> Self {
|
||||
static BASE: Lazy<TestBase> = Lazy::new(TestBase::default);
|
||||
let main = Source::detached(text);
|
||||
Self { main, base: &*BASE }
|
||||
}
|
||||
}
|
||||
|
||||
impl World for TestWorld {
|
||||
fn library(&self) -> &Prehashed<Library> {
|
||||
&self.base.library
|
||||
}
|
||||
|
||||
fn book(&self) -> &Prehashed<FontBook> {
|
||||
&self.base.book
|
||||
}
|
||||
|
||||
fn main(&self) -> Source {
|
||||
self.main.clone()
|
||||
}
|
||||
|
||||
fn source(&self, id: FileId) -> FileResult<Source> {
|
||||
if id == self.main.id() {
|
||||
Ok(self.main.clone())
|
||||
} else {
|
||||
Err(FileError::NotFound(id.vpath().as_rootless_path().into()))
|
||||
}
|
||||
}
|
||||
|
||||
fn file(&self, id: FileId) -> FileResult<Bytes> {
|
||||
Err(FileError::NotFound(id.vpath().as_rootless_path().into()))
|
||||
}
|
||||
|
||||
fn font(&self, index: usize) -> Option<Font> {
|
||||
Some(self.base.fonts[index].clone())
|
||||
}
|
||||
|
||||
fn today(&self, _: Option<i64>) -> Option<Datetime> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared foundation of all test worlds.
|
||||
struct TestBase {
|
||||
library: Prehashed<Library>,
|
||||
book: Prehashed<FontBook>,
|
||||
fonts: Vec<Font>,
|
||||
}
|
||||
|
||||
impl Default for TestBase {
|
||||
fn default() -> Self {
|
||||
let fonts: Vec<_> = typst_assets::fonts()
|
||||
.chain(typst_dev_assets::fonts())
|
||||
.flat_map(|data| Font::iter(Bytes::from_static(data)))
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
library: Prehashed::new(Library::default()),
|
||||
book: Prehashed::new(FontBook::from_fonts(&fonts)),
|
||||
fonts,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -377,7 +377,7 @@ pub struct EncodedPage {
|
||||
}
|
||||
|
||||
/// Represents a resource being used in a PDF page by its name.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
|
||||
pub struct PageResource {
|
||||
kind: ResourceKind,
|
||||
name: EcoString,
|
||||
@ -390,7 +390,7 @@ impl PageResource {
|
||||
}
|
||||
|
||||
/// A kind of resource being used in a PDF page.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
|
||||
pub enum ResourceKind {
|
||||
XObject,
|
||||
Font,
|
||||
|
@ -118,13 +118,15 @@ fn register_pattern(
|
||||
// Render the body.
|
||||
let (_, content) = construct_page(ctx.parent, pattern.frame());
|
||||
|
||||
let pdf_pattern = PdfPattern {
|
||||
let mut pdf_pattern = PdfPattern {
|
||||
transform,
|
||||
pattern: pattern.clone(),
|
||||
content: content.content.wait().clone(),
|
||||
resources: content.resources.into_iter().collect(),
|
||||
};
|
||||
|
||||
pdf_pattern.resources.sort();
|
||||
|
||||
ctx.parent.pattern_map.insert(pdf_pattern)
|
||||
}
|
||||
|
||||
|
@ -42,13 +42,13 @@ pub fn render(frame: &Frame, pixel_per_pt: f32, fill: Color) -> sk::Pixmap {
|
||||
|
||||
/// Export a document with potentially multiple pages into a single raster image.
|
||||
///
|
||||
/// The padding will be added around and between the individual frames.
|
||||
/// The gap will be added between the individual frames.
|
||||
pub fn render_merged(
|
||||
document: &Document,
|
||||
pixel_per_pt: f32,
|
||||
frame_fill: Color,
|
||||
padding: Abs,
|
||||
padding_fill: Color,
|
||||
gap: Abs,
|
||||
gap_fill: Color,
|
||||
) -> sk::Pixmap {
|
||||
let pixmaps: Vec<_> = document
|
||||
.pages
|
||||
@ -56,19 +56,18 @@ pub fn render_merged(
|
||||
.map(|page| render(&page.frame, pixel_per_pt, frame_fill))
|
||||
.collect();
|
||||
|
||||
let padding = (pixel_per_pt * padding.to_f32()).round() as u32;
|
||||
let pxw =
|
||||
2 * padding + pixmaps.iter().map(sk::Pixmap::width).max().unwrap_or_default();
|
||||
let pxh =
|
||||
padding + pixmaps.iter().map(|pixmap| pixmap.height() + padding).sum::<u32>();
|
||||
let gap = (pixel_per_pt * gap.to_f32()).round() as u32;
|
||||
let pxw = pixmaps.iter().map(sk::Pixmap::width).max().unwrap_or_default();
|
||||
let pxh = pixmaps.iter().map(|pixmap| pixmap.height()).sum::<u32>()
|
||||
+ gap * pixmaps.len().saturating_sub(1) as u32;
|
||||
|
||||
let mut canvas = sk::Pixmap::new(pxw, pxh).unwrap();
|
||||
canvas.fill(to_sk_color(padding_fill));
|
||||
canvas.fill(to_sk_color(gap_fill));
|
||||
|
||||
let [x, mut y] = [padding; 2];
|
||||
let mut y = 0;
|
||||
for pixmap in pixmaps {
|
||||
canvas.draw_pixmap(
|
||||
x as i32,
|
||||
0,
|
||||
y as i32,
|
||||
pixmap.as_ref(),
|
||||
&sk::PixmapPaint::default(),
|
||||
@ -76,7 +75,7 @@ pub fn render_merged(
|
||||
None,
|
||||
);
|
||||
|
||||
y += pixmap.height() + padding;
|
||||
y += pixmap.height() + gap;
|
||||
}
|
||||
|
||||
canvas
|
||||
|
@ -167,11 +167,6 @@ cast! {
|
||||
/// This function is not intended to be called directly. Instead, it is used
|
||||
/// in set and show rules to customize footnote listings.
|
||||
///
|
||||
/// _Note:_ Set and show rules for `footnote.entry` must be defined at the
|
||||
/// beginning of the document in order to work correctly.
|
||||
/// See [here](https://github.com/typst/typst/issues/1348#issuecomment-1566316463)
|
||||
/// for more information.
|
||||
///
|
||||
/// ```example
|
||||
/// #show footnote.entry: set text(red)
|
||||
///
|
||||
@ -179,6 +174,12 @@ cast! {
|
||||
/// #footnote[It's down here]
|
||||
/// has red text!
|
||||
/// ```
|
||||
///
|
||||
/// _Note:_ Set and show rules for `footnote.entry` must be defined at the
|
||||
/// beginning of the document in order to work correctly. See [here][issue] for
|
||||
/// more information.
|
||||
///
|
||||
/// [issue]: https://github.com/typst/typst/issues/1467#issuecomment-1588799440
|
||||
#[elem(name = "entry", title = "Footnote Entry", Show, ShowSet)]
|
||||
pub struct FootnoteEntry {
|
||||
/// The footnote for this entry. It's location can be used to determine
|
||||
|
@ -6,29 +6,29 @@ authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
publish = false
|
||||
|
||||
[dev-dependencies]
|
||||
[[test]]
|
||||
name = "tests"
|
||||
path = "src/tests.rs"
|
||||
harness = false
|
||||
|
||||
[dependencies]
|
||||
typst = { workspace = true }
|
||||
typst-assets = { workspace = true, features = ["fonts"] }
|
||||
typst-dev-assets = { workspace = true }
|
||||
typst-pdf = { workspace = true }
|
||||
typst-render = { workspace = true }
|
||||
typst-svg = { workspace = true }
|
||||
typst-ide = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
comemo = { workspace = true }
|
||||
ecow = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
oxipng = { workspace = true }
|
||||
parking_lot = { workspace = true }
|
||||
rayon = { workspace = true }
|
||||
tiny-skia = { workspace = true }
|
||||
ttf-parser = { workspace = true }
|
||||
unscanny = { workspace = true }
|
||||
walkdir = { workspace = true }
|
||||
|
||||
[[test]]
|
||||
name = "tests"
|
||||
path = "src/tests.rs"
|
||||
harness = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
@ -3,13 +3,10 @@
|
||||
## Directory structure
|
||||
Top level directory structure:
|
||||
- `src`: Testing code.
|
||||
- `typ`: Input files. The tests in `compiler` specifically test the compiler
|
||||
while the others test the standard library (but also the compiler
|
||||
indirectly).
|
||||
- `suite`: Input files. Mostly organize in parallel to the code.
|
||||
- `ref`: Reference images which the output is compared with to determine whether
|
||||
a test passed or failed.
|
||||
- `png`: PNG files produced by tests.
|
||||
- `pdf`: PDF files produced by tests.
|
||||
- `store`: Store for PNG, PDF, and SVG output files produced by the tests.
|
||||
|
||||
## Running the tests
|
||||
Running all tests (including unit tests):
|
||||
@ -37,11 +34,6 @@ Running a test with the exact filename `page.typ`.
|
||||
testit --exact page.typ
|
||||
```
|
||||
|
||||
Debug-printing the layout trees for all executed tests.
|
||||
```bash
|
||||
testit --debug empty.typ
|
||||
```
|
||||
|
||||
To make the integration tests go faster they don't generate PDFs by default.
|
||||
Pass the `--pdf` flag to generate those. Mind that PDFs are not tested
|
||||
automatically at the moment, so you should always check the output manually when
|
||||
@ -50,18 +42,48 @@ making changes.
|
||||
testit --pdf
|
||||
```
|
||||
|
||||
## Update expected images
|
||||
## Writing tests
|
||||
The syntax for an individual test is `--- {name} ---` followed by some Typst
|
||||
code that should be tested. The name must be globally unique in the test suite,
|
||||
so that tests can be easily migrated across files.
|
||||
|
||||
There are, broadly speaking, three kinds of tests:
|
||||
|
||||
- Tests that just ensure that the code runs successfully: Those typically make
|
||||
use of `test` or `assert.eq` (both are very similar, `test` is just shorter)
|
||||
to ensure certain properties hold when executing the Typst code.
|
||||
|
||||
- Tests that ensure the code fails with a particular error: Those have inline
|
||||
annotations like `// Error: 2-7 thing was wrong`. An annotation can be
|
||||
either an "Error", a "Warning", or a "Hint". The range designates where
|
||||
in the next non-comment line the error is and after it follows the message.
|
||||
If you the error is in a line further below, you can also write ranges like
|
||||
`3:2-3:7` to indicate the 2-7 column in the 3rd non-comment line.
|
||||
|
||||
- Tests that ensure certain visual output is produced: Those render the result
|
||||
of the test with the `typst-render` crate and compare against a reference
|
||||
image stored in the repository. The test runner automatically detects whether
|
||||
a test has visual output and requires a reference image in this case.
|
||||
|
||||
To prevent bloat, it is important that the test images are kept as small as
|
||||
possible. To that effect, the test runner enforces a maximum size of 20 KiB.
|
||||
If truly necessary, this limit can however be lifted by adding `// LARGE` as
|
||||
the first line of a test.
|
||||
|
||||
If you have the choice between writing a test using assertions or using
|
||||
reference images, prefer assertions. This makes the test easier to understand
|
||||
in isolation and prevents bloat due to images.
|
||||
|
||||
## Updating reference images
|
||||
If you created a new test or fixed a bug in an existing test, you need to update
|
||||
the reference image used for comparison. For this, you can use the
|
||||
`UPDATE_EXPECT` environment variable or the `--update` flag:
|
||||
the reference image used for comparison. For this, you can use the `--update`
|
||||
flag:
|
||||
```bash
|
||||
testit mytest --update
|
||||
```
|
||||
|
||||
If you use the VS Code test helper extension (see the `tools` folder), you can
|
||||
alternatively use the checkmark button to update the reference image. In that
|
||||
case you should also install `oxipng` on your system so that the test helper
|
||||
can optimize the reference images.
|
||||
alternatively use the save button to update the reference image.
|
||||
|
||||
## Making an alias
|
||||
If you want to have a quicker way to run the tests, consider adding a shortcut
|
||||
|
BIN
tests/ref/align-center-in-flow.png
Normal file
After Width: | Height: | Size: 567 B |
BIN
tests/ref/align-in-stack.png
Normal file
After Width: | Height: | Size: 158 B |
BIN
tests/ref/align-right.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
tests/ref/align-start-and-end.png
Normal file
After Width: | Height: | Size: 795 B |
BIN
tests/ref/array-basic-syntax.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
tests/ref/array-insert-and-remove.png
Normal file
After Width: | Height: | Size: 118 B |
BIN
tests/ref/array-join-content.png
Normal file
After Width: | Height: | Size: 546 B |
BIN
tests/ref/baseline-box.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
tests/ref/baseline-text.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
tests/ref/bibliography-basic.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
tests/ref/bibliography-before-content.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
tests/ref/bibliography-full.png
Normal file
After Width: | Height: | Size: 53 KiB |
BIN
tests/ref/bibliography-math.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
tests/ref/bibliography-multiple-files.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
tests/ref/bibliography-ordering.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
tests/ref/bidi-consecutive-embedded-ltr-runs.png
Normal file
After Width: | Height: | Size: 751 B |
BIN
tests/ref/bidi-consecutive-embedded-rtl-runs.png
Normal file
After Width: | Height: | Size: 548 B |
BIN
tests/ref/bidi-en-he-top-level.png
Normal file
After Width: | Height: | Size: 636 B |
BIN
tests/ref/bidi-explicit-dir.png
Normal file
After Width: | Height: | Size: 911 B |
BIN
tests/ref/bidi-manual-linebreak.png
Normal file
After Width: | Height: | Size: 966 B |
BIN
tests/ref/bidi-nesting.png
Normal file
After Width: | Height: | Size: 286 B |
BIN
tests/ref/bidi-obj.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
tests/ref/bidi-raw.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
tests/ref/bidi-spacing.png
Normal file
After Width: | Height: | Size: 461 B |
BIN
tests/ref/bidi-whitespace-reset.png
Normal file
After Width: | Height: | Size: 378 B |
BIN
tests/ref/block-box-fill.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
tests/ref/block-clip-svg-glyphs.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
tests/ref/block-clip-text.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
tests/ref/block-clipping-multiple-pages.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
tests/ref/block-fixed-height.png
Normal file
After Width: | Height: | Size: 7.0 KiB |
BIN
tests/ref/block-multiple-pages.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
tests/ref/block-sizing.png
Normal file
After Width: | Height: | Size: 139 B |
BIN
tests/ref/block-spacing-basic.png
Normal file
After Width: | Height: | Size: 733 B |
BIN
tests/ref/block-spacing-collapse-text-style.png
Normal file
After Width: | Height: | Size: 299 B |
BIN
tests/ref/block-spacing-maximum.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
tests/ref/block-spacing-table.png
Normal file
After Width: | Height: | Size: 586 B |
BIN
tests/ref/box-clip-radius-without-stroke.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
tests/ref/box-clip-radius.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
tests/ref/box-clip-rect.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
tests/ref/box-layoutable-child.png
Normal file
After Width: | Height: | Size: 257 B |
BIN
tests/ref/box-width-fr.png
Normal file
After Width: | Height: | Size: 464 B |
BIN
tests/ref/box.png
Normal file
After Width: | Height: | Size: 691 B |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 603 B |
Before Width: | Height: | Size: 473 B |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 531 B |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 144 B |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 296 B |
Before Width: | Height: | Size: 956 B |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 513 B |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 274 B |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 287 B |
Before Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 835 B |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 137 B |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.2 KiB |