Use librsvg/Cairo/Pixman to render SVG reference images.
The scale and colors aren't correct yet, but this is a start.
This commit is contained in:
parent
c2d89aba91
commit
b6236ac835
|
@ -41,7 +41,11 @@ const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
|
||||||
xcaa: AdaptiveMonochromeXCAAStrategy,
|
xcaa: AdaptiveMonochromeXCAAStrategy,
|
||||||
};
|
};
|
||||||
|
|
||||||
const RENDER_REFERENCE_URI: string = "/render-reference";
|
const RENDER_REFERENCE_URIS: PerTestType<string> = {
|
||||||
|
font: "/render-reference/text",
|
||||||
|
svg: "/render-reference/svg",
|
||||||
|
};
|
||||||
|
|
||||||
const TEST_DATA_URI: string = "/test-data/reference-test-text.csv";
|
const TEST_DATA_URI: string = "/test-data/reference-test-text.csv";
|
||||||
|
|
||||||
const SSIM_TOLERANCE: number = 0.01;
|
const SSIM_TOLERANCE: number = 0.01;
|
||||||
|
@ -95,6 +99,7 @@ class ReferenceTestAppController extends DemoAppController<ReferenceTestView> {
|
||||||
textRun: TextRun | null;
|
textRun: TextRun | null;
|
||||||
|
|
||||||
svgLoader: SVGLoader;
|
svgLoader: SVGLoader;
|
||||||
|
builtinSvgName: string | null;
|
||||||
|
|
||||||
referenceCanvas: HTMLCanvasElement;
|
referenceCanvas: HTMLCanvasElement;
|
||||||
|
|
||||||
|
@ -162,7 +167,7 @@ class ReferenceTestAppController extends DemoAppController<ReferenceTestView> {
|
||||||
this.referenceRendererSelect =
|
this.referenceRendererSelect =
|
||||||
unwrapNull(document.getElementById('pf-font-reference-renderer')) as HTMLSelectElement;
|
unwrapNull(document.getElementById('pf-font-reference-renderer')) as HTMLSelectElement;
|
||||||
this.referenceRendererSelect.addEventListener('change', () => {
|
this.referenceRendererSelect.addEventListener('change', () => {
|
||||||
this.view.then(view => this.runSingleTest());
|
this.view.then(view => this.runSingleTest(view));
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
super.start();
|
super.start();
|
||||||
|
@ -177,13 +182,13 @@ class ReferenceTestAppController extends DemoAppController<ReferenceTestView> {
|
||||||
this.fontSizeInput = unwrapNull(document.getElementById('pf-font-size')) as
|
this.fontSizeInput = unwrapNull(document.getElementById('pf-font-size')) as
|
||||||
HTMLInputElement;
|
HTMLInputElement;
|
||||||
this.fontSizeInput.addEventListener('change', () => {
|
this.fontSizeInput.addEventListener('change', () => {
|
||||||
this.view.then(view => this.runSingleTest());
|
this.view.then(view => this.runSingleTest(view));
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
this.characterInput = unwrapNull(document.getElementById('pf-character')) as
|
this.characterInput = unwrapNull(document.getElementById('pf-character')) as
|
||||||
HTMLInputElement;
|
HTMLInputElement;
|
||||||
this.characterInput.addEventListener('change', () => {
|
this.characterInput.addEventListener('change', () => {
|
||||||
this.view.then(view => this.runSingleTest());
|
this.view.then(view => this.runSingleTest(view));
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
this.aaLevelGroup = unwrapNull(document.getElementById('pf-aa-level-group')) as
|
this.aaLevelGroup = unwrapNull(document.getElementById('pf-aa-level-group')) as
|
||||||
|
@ -244,7 +249,7 @@ class ReferenceTestAppController extends DemoAppController<ReferenceTestView> {
|
||||||
this.loadInitialFile(this.builtinFileURI);
|
this.loadInitialFile(this.builtinFileURI);
|
||||||
}
|
}
|
||||||
|
|
||||||
runNextTestIfNecessary(tests: ReferenceTestGroup[]): void {
|
runNextTestIfNecessary(view: ReferenceTestView, tests: ReferenceTestGroup[]): void {
|
||||||
if (this.currentTestGroupIndex == null || this.currentTestCaseIndex == null ||
|
if (this.currentTestGroupIndex == null || this.currentTestCaseIndex == null ||
|
||||||
this.currentGlobalTestCaseIndex == null) {
|
this.currentGlobalTestCaseIndex == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -266,7 +271,7 @@ class ReferenceTestAppController extends DemoAppController<ReferenceTestView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loadFontForTestGroupIfNecessary(tests).then(() => {
|
this.loadFontForTestGroupIfNecessary(tests).then(() => {
|
||||||
this.setOptionsForCurrentTest(tests).then(() => this.runSingleTest());
|
this.setOptionsForCurrentTest(tests).then(() => this.runSingleTest(view));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,7 +333,7 @@ class ReferenceTestAppController extends DemoAppController<ReferenceTestView> {
|
||||||
|
|
||||||
// Don't automatically run the test unless this is a custom test.
|
// Don't automatically run the test unless this is a custom test.
|
||||||
if (this.currentGlobalTestCaseIndex == null)
|
if (this.currentGlobalTestCaseIndex == null)
|
||||||
this.runSingleTest();
|
this.view.then(view => this.runSingleTest(view));
|
||||||
}
|
}
|
||||||
|
|
||||||
private textFileLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
|
private textFileLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
|
||||||
|
@ -337,6 +342,7 @@ class ReferenceTestAppController extends DemoAppController<ReferenceTestView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private svgFileLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
|
private svgFileLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
|
||||||
|
this.builtinSvgName = builtinName;
|
||||||
this.svgLoader = new SVGLoader;
|
this.svgLoader = new SVGLoader;
|
||||||
this.svgLoader.loadFile(fileData);
|
this.svgLoader.loadFile(fileData);
|
||||||
}
|
}
|
||||||
|
@ -381,6 +387,7 @@ class ReferenceTestAppController extends DemoAppController<ReferenceTestView> {
|
||||||
subpixel: !!row.Subpixel,
|
subpixel: !!row.Subpixel,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return fontNames.map(fontName => {
|
return fontNames.map(fontName => {
|
||||||
return {
|
return {
|
||||||
font: fontName,
|
font: fontName,
|
||||||
|
@ -412,11 +419,11 @@ class ReferenceTestAppController extends DemoAppController<ReferenceTestView> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private runSingleTest(): void {
|
private runSingleTest(view: ReferenceTestView): void {
|
||||||
if (this.currentTestType === 'font')
|
if (this.currentTestType === 'font')
|
||||||
this.setUpTextRun();
|
this.setUpTextRun();
|
||||||
|
|
||||||
this.loadReference().then(() => this.loadRendering());
|
this.loadReference(view).then(() => this.loadRendering());
|
||||||
}
|
}
|
||||||
|
|
||||||
private runTests(): void {
|
private runTests(): void {
|
||||||
|
@ -428,7 +435,7 @@ class ReferenceTestAppController extends DemoAppController<ReferenceTestView> {
|
||||||
this.currentGlobalTestCaseIndex = 0;
|
this.currentGlobalTestCaseIndex = 0;
|
||||||
|
|
||||||
this.loadFontForTestGroupIfNecessary(tests).then(() => {
|
this.loadFontForTestGroupIfNecessary(tests).then(() => {
|
||||||
this.setOptionsForCurrentTest(tests).then(() => this.runSingleTest());
|
this.setOptionsForCurrentTest(tests).then(() => this.runSingleTest(view));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -514,8 +521,11 @@ class ReferenceTestAppController extends DemoAppController<ReferenceTestView> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadReference(): Promise<void> {
|
private loadReference(view: ReferenceTestView): Promise<void> {
|
||||||
const request = {
|
let request;
|
||||||
|
switch (this.currentTestType) {
|
||||||
|
case 'font':
|
||||||
|
request = {
|
||||||
face: {
|
face: {
|
||||||
Builtin: unwrapNull(this.font).builtinFontName,
|
Builtin: unwrapNull(this.font).builtinFontName,
|
||||||
},
|
},
|
||||||
|
@ -524,8 +534,19 @@ class ReferenceTestAppController extends DemoAppController<ReferenceTestView> {
|
||||||
pointSize: this.currentFontSize,
|
pointSize: this.currentFontSize,
|
||||||
renderer: this.currentReferenceRenderer,
|
renderer: this.currentReferenceRenderer,
|
||||||
};
|
};
|
||||||
|
break;
|
||||||
|
case 'svg':
|
||||||
|
// TODO(pcwalton): Custom SVGs.
|
||||||
|
// TODO(pcwalton): Detect scale.
|
||||||
|
request = {
|
||||||
|
name: unwrapNull(this.builtinSvgName),
|
||||||
|
renderer: 'pixman',
|
||||||
|
scale: 1.0,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
return window.fetch(RENDER_REFERENCE_URI, {
|
return window.fetch(RENDER_REFERENCE_URIS[this.currentTestType], {
|
||||||
body: JSON.stringify(request),
|
body: JSON.stringify(request),
|
||||||
headers: {'Content-Type': 'application/json'} as any,
|
headers: {'Content-Type': 'application/json'} as any,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
@ -630,7 +651,7 @@ class ReferenceTestView extends DemoView {
|
||||||
const differenceImage = generateDifferenceImage(referenceImage, renderedImage);
|
const differenceImage = generateDifferenceImage(referenceImage, renderedImage);
|
||||||
this.appController.recordSSIMResult(tests, ssimResult);
|
this.appController.recordSSIMResult(tests, ssimResult);
|
||||||
this.appController.drawDifferenceImage(differenceImage);
|
this.appController.drawDifferenceImage(differenceImage);
|
||||||
this.appController.runNextTestIfNecessary(tests);
|
this.appController.runNextTestIfNecessary(this, tests);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,10 +16,15 @@ lru-cache = "0.1"
|
||||||
rocket = "0.3"
|
rocket = "0.3"
|
||||||
rocket_codegen = "0.3"
|
rocket_codegen = "0.3"
|
||||||
rocket_contrib = "0.3"
|
rocket_contrib = "0.3"
|
||||||
|
rsvg = "0.3"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
|
||||||
|
[dependencies.cairo-rs]
|
||||||
|
version = "0.3"
|
||||||
|
features = ["png"]
|
||||||
|
|
||||||
[dependencies.fontsan]
|
[dependencies.fontsan]
|
||||||
git = "https://github.com/servo/fontsan.git"
|
git = "https://github.com/servo/fontsan.git"
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
extern crate app_units;
|
extern crate app_units;
|
||||||
extern crate base64;
|
extern crate base64;
|
||||||
|
extern crate cairo;
|
||||||
extern crate env_logger;
|
extern crate env_logger;
|
||||||
extern crate euclid;
|
extern crate euclid;
|
||||||
extern crate fontsan;
|
extern crate fontsan;
|
||||||
|
@ -23,6 +24,7 @@ extern crate pathfinder_partitioner;
|
||||||
extern crate pathfinder_path_utils;
|
extern crate pathfinder_path_utils;
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
extern crate rocket_contrib;
|
extern crate rocket_contrib;
|
||||||
|
extern crate rsvg;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
|
@ -30,7 +32,8 @@ extern crate lazy_static;
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
|
|
||||||
use app_units::Au;
|
use app_units::Au;
|
||||||
use euclid::{Point2D, Transform2D};
|
use cairo::{Format, ImageSurface};
|
||||||
|
use euclid::{Point2D, Size2D, Transform2D};
|
||||||
use image::{DynamicImage, ImageBuffer, ImageFormat, ImageRgba8};
|
use image::{DynamicImage, ImageBuffer, ImageFormat, ImageRgba8};
|
||||||
use lru_cache::LruCache;
|
use lru_cache::LruCache;
|
||||||
use pathfinder_font_renderer::{FontContext, FontInstance, FontKey, GlyphImage};
|
use pathfinder_font_renderer::{FontContext, FontInstance, FontKey, GlyphImage};
|
||||||
|
@ -46,6 +49,7 @@ use rocket::http::{ContentType, Header, Status};
|
||||||
use rocket::request::Request;
|
use rocket::request::Request;
|
||||||
use rocket::response::{NamedFile, Redirect, Responder, Response};
|
use rocket::response::{NamedFile, Redirect, Responder, Response};
|
||||||
use rocket_contrib::json::Json;
|
use rocket_contrib::json::Json;
|
||||||
|
use rsvg::{Handle, HandleExt};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, Cursor, Read};
|
use std::io::{self, Cursor, Read};
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
@ -130,15 +134,21 @@ enum FontRequestFace {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Serialize, Deserialize)]
|
||||||
enum ReferenceRenderer {
|
enum ReferenceTextRenderer {
|
||||||
#[serde(rename = "freetype")]
|
#[serde(rename = "freetype")]
|
||||||
FreeType,
|
FreeType,
|
||||||
#[serde(rename = "core-graphics")]
|
#[serde(rename = "core-graphics")]
|
||||||
CoreGraphics,
|
CoreGraphics,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Serialize, Deserialize)]
|
||||||
|
enum ReferenceSvgRenderer {
|
||||||
|
#[serde(rename = "pixman")]
|
||||||
|
Pixman,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
struct RenderReferenceRequest {
|
struct RenderTextReferenceRequest {
|
||||||
face: FontRequestFace,
|
face: FontRequestFace,
|
||||||
#[serde(rename = "fontIndex")]
|
#[serde(rename = "fontIndex")]
|
||||||
font_index: u32,
|
font_index: u32,
|
||||||
|
@ -146,7 +156,14 @@ struct RenderReferenceRequest {
|
||||||
|
|
||||||
#[serde(rename = "pointSize")]
|
#[serde(rename = "pointSize")]
|
||||||
point_size: f64,
|
point_size: f64,
|
||||||
renderer: ReferenceRenderer,
|
renderer: ReferenceTextRenderer,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
struct RenderSvgReferenceRequest {
|
||||||
|
name: String,
|
||||||
|
scale: f64,
|
||||||
|
renderer: ReferenceSvgRenderer,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Serialize, Deserialize)]
|
||||||
|
@ -172,6 +189,13 @@ enum FontError {
|
||||||
Unimplemented,
|
Unimplemented,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||||
|
enum SvgError {
|
||||||
|
UnknownBuiltinSvg,
|
||||||
|
LoadingFailed,
|
||||||
|
ImageWritingFailed,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||||
enum PartitionSvgPathsError {
|
enum PartitionSvgPathsError {
|
||||||
UnknownSvgPathCommandType,
|
UnknownSvgPathCommandType,
|
||||||
|
@ -327,6 +351,21 @@ fn otf_data_from_request(face: &FontRequestFace) -> Result<Arc<Vec<u8>>, FontErr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetches the SVG data.
|
||||||
|
fn svg_data_from_request(builtin_svg_name: &str) -> Result<Arc<Vec<u8>>, SvgError> {
|
||||||
|
// Read in the builtin SVG.
|
||||||
|
match BUILTIN_SVGS.iter().filter(|& &(name, _)| name == builtin_svg_name).next() {
|
||||||
|
Some(&(_, path)) => {
|
||||||
|
let mut data = vec![];
|
||||||
|
File::open(path).expect("Couldn't find builtin SVG!")
|
||||||
|
.read_to_end(&mut data)
|
||||||
|
.expect("Couldn't read builtin SVG!");
|
||||||
|
Ok(Arc::new(data))
|
||||||
|
}
|
||||||
|
None => return Err(SvgError::UnknownBuiltinSvg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
fn rasterize_glyph_with_core_graphics(font_key: &FontKey,
|
fn rasterize_glyph_with_core_graphics(font_key: &FontKey,
|
||||||
font_index: u32,
|
font_index: u32,
|
||||||
|
@ -536,8 +575,8 @@ fn partition_svg_paths(request: Json<PartitionSvgPathsRequest>)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/render-reference", format = "application/json", data = "<request>")]
|
#[post("/render-reference/text", format = "application/json", data = "<request>")]
|
||||||
fn render_reference(request: Json<RenderReferenceRequest>)
|
fn render_reference_text(request: Json<RenderTextReferenceRequest>)
|
||||||
-> Result<ReferenceImage, FontError> {
|
-> Result<ReferenceImage, FontError> {
|
||||||
let font_key = FontKey::new();
|
let font_key = FontKey::new();
|
||||||
let otf_data = try!(otf_data_from_request(&request.face));
|
let otf_data = try!(otf_data_from_request(&request.face));
|
||||||
|
@ -549,7 +588,7 @@ fn render_reference(request: Json<RenderReferenceRequest>)
|
||||||
|
|
||||||
// Rasterize the glyph using the right rasterizer.
|
// Rasterize the glyph using the right rasterizer.
|
||||||
let glyph_image = match request.renderer {
|
let glyph_image = match request.renderer {
|
||||||
ReferenceRenderer::FreeType => {
|
ReferenceTextRenderer::FreeType => {
|
||||||
let mut font_context =
|
let mut font_context =
|
||||||
try!(FontContext::new().map_err(|_| FontError::FontLoadingFailed));
|
try!(FontContext::new().map_err(|_| FontError::FontLoadingFailed));
|
||||||
try!(font_context.add_font_from_memory(&font_key, otf_data, request.font_index)
|
try!(font_context.add_font_from_memory(&font_key, otf_data, request.font_index)
|
||||||
|
@ -559,7 +598,7 @@ fn render_reference(request: Json<RenderReferenceRequest>)
|
||||||
true)
|
true)
|
||||||
.map_err(|_| FontError::RasterizationFailed))
|
.map_err(|_| FontError::RasterizationFailed))
|
||||||
}
|
}
|
||||||
ReferenceRenderer::CoreGraphics => {
|
ReferenceTextRenderer::CoreGraphics => {
|
||||||
try!(rasterize_glyph_with_core_graphics(&font_key,
|
try!(rasterize_glyph_with_core_graphics(&font_key,
|
||||||
request.font_index,
|
request.font_index,
|
||||||
otf_data,
|
otf_data,
|
||||||
|
@ -579,6 +618,41 @@ fn render_reference(request: Json<RenderReferenceRequest>)
|
||||||
Ok(reference_image)
|
Ok(reference_image)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[post("/render-reference/svg", format = "application/json", data = "<request>")]
|
||||||
|
fn render_reference_svg(request: Json<RenderSvgReferenceRequest>)
|
||||||
|
-> Result<ReferenceImage, SvgError> {
|
||||||
|
let svg_data = try!(svg_data_from_request(&request.name));
|
||||||
|
let svg_string = String::from_utf8_lossy(&*svg_data);
|
||||||
|
let svg_handle = try!(Handle::new_from_str(&svg_string).map_err(|_| SvgError::LoadingFailed));
|
||||||
|
|
||||||
|
let svg_dimensions = svg_handle.get_dimensions();
|
||||||
|
let mut image_size = Size2D::new(svg_dimensions.width as f64, svg_dimensions.height as f64);
|
||||||
|
image_size = (image_size * request.scale).ceil();
|
||||||
|
|
||||||
|
// Rasterize the SVG using the appropriate rasterizer.
|
||||||
|
let mut surface = ImageSurface::create(Format::ARgb32,
|
||||||
|
image_size.width as i32,
|
||||||
|
image_size.height as i32).unwrap();
|
||||||
|
|
||||||
|
{
|
||||||
|
let cairo_context = cairo::Context::new(&surface);
|
||||||
|
cairo_context.scale(request.scale, request.scale);
|
||||||
|
svg_handle.render_cairo(&cairo_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
let image_data = (*surface.get_data().unwrap()).to_vec();
|
||||||
|
let image_buffer = match ImageBuffer::from_raw(image_size.width as u32,
|
||||||
|
image_size.height as u32,
|
||||||
|
image_data) {
|
||||||
|
None => return Err(SvgError::ImageWritingFailed),
|
||||||
|
Some(image_buffer) => image_buffer,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ReferenceImage {
|
||||||
|
image: ImageRgba8(image_buffer),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Static files
|
// Static files
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
fn static_index() -> io::Result<NamedFile> {
|
fn static_index() -> io::Result<NamedFile> {
|
||||||
|
@ -715,7 +789,8 @@ fn main() {
|
||||||
rocket.mount("/", routes![
|
rocket.mount("/", routes![
|
||||||
partition_font,
|
partition_font,
|
||||||
partition_svg_paths,
|
partition_svg_paths,
|
||||||
render_reference,
|
render_reference_text,
|
||||||
|
render_reference_svg,
|
||||||
static_index,
|
static_index,
|
||||||
static_demo_text,
|
static_demo_text,
|
||||||
static_demo_svg,
|
static_demo_svg,
|
||||||
|
|
Loading…
Reference in New Issue