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,
|
||||
};
|
||||
|
||||
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 SSIM_TOLERANCE: number = 0.01;
|
||||
|
@ -95,6 +99,7 @@ class ReferenceTestAppController extends DemoAppController<ReferenceTestView> {
|
|||
textRun: TextRun | null;
|
||||
|
||||
svgLoader: SVGLoader;
|
||||
builtinSvgName: string | null;
|
||||
|
||||
referenceCanvas: HTMLCanvasElement;
|
||||
|
||||
|
@ -162,7 +167,7 @@ class ReferenceTestAppController extends DemoAppController<ReferenceTestView> {
|
|||
this.referenceRendererSelect =
|
||||
unwrapNull(document.getElementById('pf-font-reference-renderer')) as HTMLSelectElement;
|
||||
this.referenceRendererSelect.addEventListener('change', () => {
|
||||
this.view.then(view => this.runSingleTest());
|
||||
this.view.then(view => this.runSingleTest(view));
|
||||
}, false);
|
||||
|
||||
super.start();
|
||||
|
@ -177,13 +182,13 @@ class ReferenceTestAppController extends DemoAppController<ReferenceTestView> {
|
|||
this.fontSizeInput = unwrapNull(document.getElementById('pf-font-size')) as
|
||||
HTMLInputElement;
|
||||
this.fontSizeInput.addEventListener('change', () => {
|
||||
this.view.then(view => this.runSingleTest());
|
||||
this.view.then(view => this.runSingleTest(view));
|
||||
}, false);
|
||||
|
||||
this.characterInput = unwrapNull(document.getElementById('pf-character')) as
|
||||
HTMLInputElement;
|
||||
this.characterInput.addEventListener('change', () => {
|
||||
this.view.then(view => this.runSingleTest());
|
||||
this.view.then(view => this.runSingleTest(view));
|
||||
}, false);
|
||||
|
||||
this.aaLevelGroup = unwrapNull(document.getElementById('pf-aa-level-group')) as
|
||||
|
@ -244,7 +249,7 @@ class ReferenceTestAppController extends DemoAppController<ReferenceTestView> {
|
|||
this.loadInitialFile(this.builtinFileURI);
|
||||
}
|
||||
|
||||
runNextTestIfNecessary(tests: ReferenceTestGroup[]): void {
|
||||
runNextTestIfNecessary(view: ReferenceTestView, tests: ReferenceTestGroup[]): void {
|
||||
if (this.currentTestGroupIndex == null || this.currentTestCaseIndex == null ||
|
||||
this.currentGlobalTestCaseIndex == null) {
|
||||
return;
|
||||
|
@ -266,7 +271,7 @@ class ReferenceTestAppController extends DemoAppController<ReferenceTestView> {
|
|||
}
|
||||
|
||||
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.
|
||||
if (this.currentGlobalTestCaseIndex == null)
|
||||
this.runSingleTest();
|
||||
this.view.then(view => this.runSingleTest(view));
|
||||
}
|
||||
|
||||
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 {
|
||||
this.builtinSvgName = builtinName;
|
||||
this.svgLoader = new SVGLoader;
|
||||
this.svgLoader.loadFile(fileData);
|
||||
}
|
||||
|
@ -381,6 +387,7 @@ class ReferenceTestAppController extends DemoAppController<ReferenceTestView> {
|
|||
subpixel: !!row.Subpixel,
|
||||
});
|
||||
}
|
||||
|
||||
return fontNames.map(fontName => {
|
||||
return {
|
||||
font: fontName,
|
||||
|
@ -412,11 +419,11 @@ class ReferenceTestAppController extends DemoAppController<ReferenceTestView> {
|
|||
});
|
||||
}
|
||||
|
||||
private runSingleTest(): void {
|
||||
private runSingleTest(view: ReferenceTestView): void {
|
||||
if (this.currentTestType === 'font')
|
||||
this.setUpTextRun();
|
||||
|
||||
this.loadReference().then(() => this.loadRendering());
|
||||
this.loadReference(view).then(() => this.loadRendering());
|
||||
}
|
||||
|
||||
private runTests(): void {
|
||||
|
@ -428,7 +435,7 @@ class ReferenceTestAppController extends DemoAppController<ReferenceTestView> {
|
|||
this.currentGlobalTestCaseIndex = 0;
|
||||
|
||||
this.loadFontForTestGroupIfNecessary(tests).then(() => {
|
||||
this.setOptionsForCurrentTest(tests).then(() => this.runSingleTest());
|
||||
this.setOptionsForCurrentTest(tests).then(() => this.runSingleTest(view));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -514,18 +521,32 @@ class ReferenceTestAppController extends DemoAppController<ReferenceTestView> {
|
|||
});
|
||||
}
|
||||
|
||||
private loadReference(): Promise<void> {
|
||||
const request = {
|
||||
face: {
|
||||
Builtin: unwrapNull(this.font).builtinFontName,
|
||||
},
|
||||
fontIndex: 0,
|
||||
glyph: this.glyphStore.glyphIDs[0],
|
||||
pointSize: this.currentFontSize,
|
||||
renderer: this.currentReferenceRenderer,
|
||||
};
|
||||
private loadReference(view: ReferenceTestView): Promise<void> {
|
||||
let request;
|
||||
switch (this.currentTestType) {
|
||||
case 'font':
|
||||
request = {
|
||||
face: {
|
||||
Builtin: unwrapNull(this.font).builtinFontName,
|
||||
},
|
||||
fontIndex: 0,
|
||||
glyph: this.glyphStore.glyphIDs[0],
|
||||
pointSize: this.currentFontSize,
|
||||
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),
|
||||
headers: {'Content-Type': 'application/json'} as any,
|
||||
method: 'POST',
|
||||
|
@ -630,7 +651,7 @@ class ReferenceTestView extends DemoView {
|
|||
const differenceImage = generateDifferenceImage(referenceImage, renderedImage);
|
||||
this.appController.recordSSIMResult(tests, ssimResult);
|
||||
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_codegen = "0.3"
|
||||
rocket_contrib = "0.3"
|
||||
rsvg = "0.3"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
|
||||
[dependencies.cairo-rs]
|
||||
version = "0.3"
|
||||
features = ["png"]
|
||||
|
||||
[dependencies.fontsan]
|
||||
git = "https://github.com/servo/fontsan.git"
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
extern crate app_units;
|
||||
extern crate base64;
|
||||
extern crate cairo;
|
||||
extern crate env_logger;
|
||||
extern crate euclid;
|
||||
extern crate fontsan;
|
||||
|
@ -23,6 +24,7 @@ extern crate pathfinder_partitioner;
|
|||
extern crate pathfinder_path_utils;
|
||||
extern crate rocket;
|
||||
extern crate rocket_contrib;
|
||||
extern crate rsvg;
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
@ -30,7 +32,8 @@ extern crate lazy_static;
|
|||
extern crate serde_derive;
|
||||
|
||||
use app_units::Au;
|
||||
use euclid::{Point2D, Transform2D};
|
||||
use cairo::{Format, ImageSurface};
|
||||
use euclid::{Point2D, Size2D, Transform2D};
|
||||
use image::{DynamicImage, ImageBuffer, ImageFormat, ImageRgba8};
|
||||
use lru_cache::LruCache;
|
||||
use pathfinder_font_renderer::{FontContext, FontInstance, FontKey, GlyphImage};
|
||||
|
@ -46,6 +49,7 @@ use rocket::http::{ContentType, Header, Status};
|
|||
use rocket::request::Request;
|
||||
use rocket::response::{NamedFile, Redirect, Responder, Response};
|
||||
use rocket_contrib::json::Json;
|
||||
use rsvg::{Handle, HandleExt};
|
||||
use std::fs::File;
|
||||
use std::io::{self, Cursor, Read};
|
||||
use std::ops::Range;
|
||||
|
@ -130,15 +134,21 @@ enum FontRequestFace {
|
|||
}
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize)]
|
||||
enum ReferenceRenderer {
|
||||
enum ReferenceTextRenderer {
|
||||
#[serde(rename = "freetype")]
|
||||
FreeType,
|
||||
#[serde(rename = "core-graphics")]
|
||||
CoreGraphics,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize)]
|
||||
enum ReferenceSvgRenderer {
|
||||
#[serde(rename = "pixman")]
|
||||
Pixman,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
struct RenderReferenceRequest {
|
||||
struct RenderTextReferenceRequest {
|
||||
face: FontRequestFace,
|
||||
#[serde(rename = "fontIndex")]
|
||||
font_index: u32,
|
||||
|
@ -146,7 +156,14 @@ struct RenderReferenceRequest {
|
|||
|
||||
#[serde(rename = "pointSize")]
|
||||
point_size: f64,
|
||||
renderer: ReferenceRenderer,
|
||||
renderer: ReferenceTextRenderer,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
struct RenderSvgReferenceRequest {
|
||||
name: String,
|
||||
scale: f64,
|
||||
renderer: ReferenceSvgRenderer,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize)]
|
||||
|
@ -172,6 +189,13 @@ enum FontError {
|
|||
Unimplemented,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
enum SvgError {
|
||||
UnknownBuiltinSvg,
|
||||
LoadingFailed,
|
||||
ImageWritingFailed,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
enum PartitionSvgPathsError {
|
||||
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")]
|
||||
fn rasterize_glyph_with_core_graphics(font_key: &FontKey,
|
||||
font_index: u32,
|
||||
|
@ -536,9 +575,9 @@ fn partition_svg_paths(request: Json<PartitionSvgPathsRequest>)
|
|||
})
|
||||
}
|
||||
|
||||
#[post("/render-reference", format = "application/json", data = "<request>")]
|
||||
fn render_reference(request: Json<RenderReferenceRequest>)
|
||||
-> Result<ReferenceImage, FontError> {
|
||||
#[post("/render-reference/text", format = "application/json", data = "<request>")]
|
||||
fn render_reference_text(request: Json<RenderTextReferenceRequest>)
|
||||
-> Result<ReferenceImage, FontError> {
|
||||
let font_key = FontKey::new();
|
||||
let otf_data = try!(otf_data_from_request(&request.face));
|
||||
let font_instance = FontInstance {
|
||||
|
@ -549,7 +588,7 @@ fn render_reference(request: Json<RenderReferenceRequest>)
|
|||
|
||||
// Rasterize the glyph using the right rasterizer.
|
||||
let glyph_image = match request.renderer {
|
||||
ReferenceRenderer::FreeType => {
|
||||
ReferenceTextRenderer::FreeType => {
|
||||
let mut font_context =
|
||||
try!(FontContext::new().map_err(|_| FontError::FontLoadingFailed));
|
||||
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)
|
||||
.map_err(|_| FontError::RasterizationFailed))
|
||||
}
|
||||
ReferenceRenderer::CoreGraphics => {
|
||||
ReferenceTextRenderer::CoreGraphics => {
|
||||
try!(rasterize_glyph_with_core_graphics(&font_key,
|
||||
request.font_index,
|
||||
otf_data,
|
||||
|
@ -579,6 +618,41 @@ fn render_reference(request: Json<RenderReferenceRequest>)
|
|||
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
|
||||
#[get("/")]
|
||||
fn static_index() -> io::Result<NamedFile> {
|
||||
|
@ -715,7 +789,8 @@ fn main() {
|
|||
rocket.mount("/", routes![
|
||||
partition_font,
|
||||
partition_svg_paths,
|
||||
render_reference,
|
||||
render_reference_text,
|
||||
render_reference_svg,
|
||||
static_index,
|
||||
static_demo_text,
|
||||
static_demo_svg,
|
||||
|
|
Loading…
Reference in New Issue