Remove old demo server; remove `SegmentFlags`

This commit is contained in:
Patrick Walton 2019-01-10 20:43:03 -08:00
parent 7bc62bb5af
commit 3165a4a1b0
6 changed files with 177 additions and 2296 deletions

1294
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -2,13 +2,7 @@
members = [ members = [
"geometry", "geometry",
"gfx-utils", "gfx-utils",
"partitioner",
"demo/server",
"utils/area-lut", "utils/area-lut",
"utils/frontend",
"utils/gamma-lut", "utils/gamma-lut",
"utils/tile-svg", "utils/tile-svg",
] ]
[patch.crates-io]
ring = { git = "https://github.com/SergioBenitez/ring", branch = "v0.12" }

View File

@ -1,52 +0,0 @@
[package]
name = "pathfinder_server"
version = "0.1.0"
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
[features]
default = []
freetype = ["font-kit/loader-freetype-default"]
reftests = ["rsvg", "cairo-rs", "font-kit/loader-freetype-default"]
[dependencies]
app_units = "0.7"
base64 = "0.6"
bincode = "1.0"
env_logger = "0.5"
euclid = "0.19"
image = "0.19"
lazy_static = "0.2"
log = "0.3"
lru-cache = "0.1"
lyon_geom = "0.12"
lyon_path = "0.12"
rocket = "0.3"
rocket_codegen = "0.3"
rocket_contrib = "0.3"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
[dependencies.cairo-rs]
version = "0.3"
features = ["png"]
optional = true
[dependencies.rsvg]
version = "0.3"
optional = true
[dependencies.fontsan]
git = "https://github.com/servo/fontsan.git"
[dependencies.font-kit]
git = "https://github.com/pcwalton/font-kit"
[dependencies.pathfinder_geometry]
path = "../../geometry"
[dependencies.pathfinder_partitioner]
path = "../../partitioner"
[patch.crates-io]
ring = { git = "https://github.com/SergioBenitez/ring", branch = "v0.12" }

View File

@ -1,3 +0,0 @@
{
"lockfileVersion": 1
}

View File

@ -1,952 +0,0 @@
// pathfinder/demo/server/main.rs
//
// Copyright © 2018 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![feature(decl_macro, plugin)]
#![plugin(rocket_codegen)]
extern crate app_units;
extern crate base64;
extern crate env_logger;
extern crate euclid;
extern crate font_kit;
extern crate fontsan;
extern crate image;
extern crate lru_cache;
extern crate lyon_geom;
extern crate lyon_path;
extern crate pathfinder_geometry;
extern crate pathfinder_partitioner;
extern crate rocket;
extern crate rocket_contrib;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate serde_derive;
#[cfg(feature = "reftests")]
extern crate cairo;
#[cfg(feature = "reftests")]
extern crate rsvg;
use euclid::{Point2D, Transform2D};
use font_kit::canvas::Format as FontKitFormat;
use font_kit::canvas::{Canvas, RasterizationOptions};
use font_kit::font::Font;
use font_kit::hinting::HintingOptions;
use font_kit::loaders;
use image::{DynamicImage, ImageBuffer, ImageFormat, ImageRgba8};
use lru_cache::LruCache;
use lyon_path::PathEvent;
use lyon_path::builder::{FlatPathBuilder, PathBuilder};
use pathfinder_geometry::cubic_to_quadratic::CubicToQuadraticTransformer;
use pathfinder_geometry::stroke::{StrokeStyle, StrokeToFillIter};
use pathfinder_geometry::transform::Transform2DPathIter;
use lyon_path::iterator::PathIter;
use pathfinder_partitioner::FillRule;
use pathfinder_partitioner::mesh::Mesh;
use pathfinder_partitioner::mesh_pack::MeshPack;
use pathfinder_partitioner::partitioner::Partitioner;
use rocket::http::{ContentType, Header, Status};
use rocket::request::Request;
use rocket::response::{NamedFile, Redirect, Responder, Response};
use rocket_contrib::json::Json;
use std::fs::File;
use std::io::{self, Cursor, Read};
use std::path::{self, PathBuf};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use std::u32;
#[cfg(feature = "reftests")]
use euclid::Size2D;
#[cfg(feature = "reftests")]
use cairo::{Format, ImageSurface};
#[cfg(feature = "reftests")]
use rsvg::{Handle, HandleExt};
const SUGGESTED_JSON_SIZE_LIMIT: u64 = 32 * 1024 * 1024;
const MESH_PACK_CACHE_SIZE: usize = 16;
const CUBIC_TO_QUADRATIC_APPROX_TOLERANCE: f32 = 5.0;
lazy_static! {
static ref MESH_PACK_CACHE: Mutex<LruCache<MeshPackCacheKey, PartitionResponder>> = {
Mutex::new(LruCache::new(MESH_PACK_CACHE_SIZE))
};
}
static STATIC_INDEX_PATH: &'static str = "../client/index.html";
static STATIC_TEXT_DEMO_PATH: &'static str = "../client/text-demo.html";
static STATIC_SVG_DEMO_PATH: &'static str = "../client/svg-demo.html";
static STATIC_3D_DEMO_PATH: &'static str = "../client/3d-demo.html";
static STATIC_TOOLS_BENCHMARK_PATH: &'static str = "../client/benchmark.html";
static STATIC_TOOLS_REFERENCE_TEST_PATH: &'static str = "../client/reference-test.html";
static STATIC_TOOLS_MESH_DEBUGGER_PATH: &'static str = "../client/mesh-debugger.html";
static STATIC_DOC_API_PATH: &'static str = "../../target/doc";
static STATIC_CSS_BOOTSTRAP_PATH: &'static str = "../client/node_modules/bootstrap/dist/css";
static STATIC_CSS_PATH: &'static str = "../client/css";
static STATIC_JS_BOOTSTRAP_PATH: &'static str = "../client/node_modules/bootstrap/dist/js";
static STATIC_JS_JQUERY_PATH: &'static str = "../client/node_modules/jquery/dist";
static STATIC_JS_POPPER_JS_PATH: &'static str = "../client/node_modules/popper.js/dist/umd";
static STATIC_JS_PATHFINDER_PATH: &'static str = "../client";
static STATIC_WOFF2_INTER_UI_PATH: &'static str = "../../resources/fonts/inter-ui";
static STATIC_WOFF2_MATERIAL_ICONS_PATH: &'static str = "../../resources/fonts/material-icons";
static STATIC_GLSL_PATH: &'static str = "../../shaders";
static STATIC_DATA_PATH: &'static str = "../../resources/data";
static STATIC_TEST_DATA_PATH: &'static str = "../../resources/tests";
static STATIC_TEXTURES_PATH: &'static str = "../../resources/textures";
static STATIC_DOC_API_INDEX_URI: &'static str = "/doc/api/pathfinder/index.html";
static BUILTIN_FONTS: [(&'static str, &'static str); 4] = [
("open-sans", "../../resources/fonts/open-sans/OpenSans-Regular.ttf"),
("nimbus-sans", "../../resources/fonts/nimbus-sans/NimbusSanL-Regu.ttf"),
("eb-garamond", "../../resources/fonts/eb-garamond/EBGaramond12-Regular.ttf"),
("inter-ui", "../../resources/fonts/inter-ui/Inter-UI-Regular.ttf"),
];
static BUILTIN_SVGS: [(&'static str, &'static str); 4] = [
("tiger", "../../resources/svg/Ghostscript_Tiger.svg"),
("logo", "../../resources/svg/pathfinder_logo.svg"),
("icons", "../../resources/svg/material_design_icons.svg"),
("logo-bw", "../../resources/svg/pathfinder_logo_bw.svg"),
];
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
struct MeshPackCacheKey {
builtin_font_name: String,
glyph_ids: Vec<u32>,
}
#[derive(Clone, Serialize, Deserialize)]
struct PartitionFontRequest {
face: FontRequestFace,
#[serde(rename = "fontIndex")]
font_index: u32,
glyphs: Vec<PartitionGlyph>,
#[serde(rename = "pointSize")]
point_size: f64,
}
#[derive(Clone, Serialize, Deserialize)]
enum FontRequestFace {
/// One of the builtin fonts in `BUILTIN_FONTS`.
Builtin(String),
/// Base64-encoded OTF data.
Custom(String),
}
#[derive(Clone, Copy, Serialize, Deserialize)]
enum ReferenceTextRenderer {
#[serde(rename = "freetype")]
FreeType,
#[serde(rename = "core-graphics")]
#[cfg(target_os = "macos")]
CoreGraphics,
}
#[derive(Clone, Copy, Serialize, Deserialize)]
enum ReferenceSvgRenderer {
#[serde(rename = "pixman")]
Pixman,
}
#[derive(Clone, Serialize, Deserialize)]
struct RenderTextReferenceRequest {
face: FontRequestFace,
#[serde(rename = "fontIndex")]
font_index: u32,
glyph: u32,
#[serde(rename = "pointSize")]
point_size: f64,
renderer: ReferenceTextRenderer,
}
#[derive(Clone, Serialize, Deserialize)]
struct RenderSvgReferenceRequest {
name: String,
scale: f64,
renderer: ReferenceSvgRenderer,
}
#[derive(Clone, Copy, Serialize, Deserialize)]
struct PartitionGlyph {
id: u32,
transform: Transform2D<f32>,
}
#[derive(Clone, Serialize, Deserialize)]
struct PartitionFontResponse {
#[serde(rename = "pathData")]
path_data: String,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
enum FontError {
UnknownBuiltinFont,
Base64DecodingFailed,
FontSanitizationFailed,
FontLoadingFailed,
RasterizationFailed,
ReferenceRasterizerUnavailable,
Unimplemented,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
enum SvgError {
ReftestsDisabled,
UnknownBuiltinSvg,
LoadingFailed,
ImageWritingFailed,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
enum PartitionSvgPathsError {
UnknownSvgPathCommandType,
Unimplemented,
}
#[derive(Clone, Serialize, Deserialize)]
struct PartitionSvgPathsRequest {
paths: Vec<PartitionSvgPath>,
#[serde(rename = "viewBoxWidth")]
view_box_width: f32,
#[serde(rename = "viewBoxHeight")]
view_box_height: f32,
}
#[derive(Clone, Serialize, Deserialize)]
struct PartitionSvgPath {
segments: Vec<PartitionSvgPathCommand>,
kind: PartitionSvgPathKind,
}
#[derive(Clone, Copy, Serialize, Deserialize)]
enum PartitionSvgPathKind {
Fill(PartitionSvgFillRule),
Stroke(f32),
}
#[derive(Clone, Copy, Serialize, Deserialize)]
enum PartitionSvgFillRule {
Winding,
EvenOdd,
}
impl PartitionSvgFillRule {
fn to_fill_rule(self) -> FillRule {
match self {
PartitionSvgFillRule::Winding => FillRule::Winding,
PartitionSvgFillRule::EvenOdd => FillRule::EvenOdd,
}
}
}
#[derive(Clone)]
struct PathDescriptor {
path_index: usize,
fill_rule: FillRule,
}
#[derive(Clone, Serialize, Deserialize)]
struct PartitionSvgPathCommand {
#[serde(rename = "type")]
kind: char,
values: Vec<f64>,
}
struct PathPartitioningResult {
encoded_data: Arc<Vec<u8>>,
time: Duration,
}
impl PathPartitioningResult {
fn compute(pack: &mut MeshPack,
path_descriptors: &[PathDescriptor],
paths: &[Vec<PathEvent>],
approx_tolerance: Option<f32>,
tile: bool)
-> PathPartitioningResult {
let timestamp_before = Instant::now();
for (path, path_descriptor) in paths.iter().zip(path_descriptors.iter()) {
if !tile {
let mut partitioner = Partitioner::new();
if let Some(tolerance) = approx_tolerance {
partitioner.builder_mut().set_approx_tolerance(tolerance);
}
path.iter().for_each(|event| partitioner.builder_mut().path_event(*event));
partitioner.partition(path_descriptor.fill_rule);
partitioner.builder_mut().build_and_reset();
partitioner.mesh_mut().push_stencil_segments(
CubicToQuadraticTransformer::new(path.iter().cloned(),
CUBIC_TO_QUADRATIC_APPROX_TOLERANCE));
partitioner.mesh_mut().push_stencil_normals(
CubicToQuadraticTransformer::new(path.iter().cloned(),
CUBIC_TO_QUADRATIC_APPROX_TOLERANCE));
pack.push(partitioner.into_mesh());
} else {
let tiles = tiling::tile_path(path);
for tile_info in tiles {
let mut mesh = Mesh::new();
mesh.push_stencil_segments(tile_info.events.into_iter());
pack.push(mesh);
}
}
}
let time_elapsed = timestamp_before.elapsed();
eprintln!("path partitioning time: {}ms",
time_elapsed.as_secs() as f64 * 1000.0 +
time_elapsed.subsec_nanos() as f64 * 1e-6);
let mut data_buffer = Cursor::new(vec![]);
drop(pack.serialize_into(&mut data_buffer));
PathPartitioningResult {
encoded_data: Arc::new(data_buffer.into_inner()),
time: time_elapsed,
}
}
fn elapsed_ms(&self) -> f64 {
self.time.as_secs() as f64 * 1000.0 + self.time.subsec_nanos() as f64 * 1e-6
}
}
#[derive(Clone)]
struct PartitionResponder {
data: Arc<Vec<u8>>,
time: f64,
}
impl<'r> Responder<'r> for PartitionResponder {
fn respond_to(self, _: &Request) -> Result<Response<'r>, Status> {
let mut builder = Response::build();
builder.header(ContentType::new("application", "vnd.mozilla.pfml"));
builder.header(Header::new("Server-Timing", format!("Partitioning={}", self.time)));
// FIXME(pcwalton): Don't clone! Requires a `Cursor` implementation for `Arc<Vec<u8>>`…
builder.sized_body(Cursor::new((*self.data).clone()));
builder.ok()
}
}
#[derive(Clone)]
struct ReferenceImage {
image: DynamicImage,
}
impl<'r> Responder<'r> for ReferenceImage {
fn respond_to(self, _: &Request) -> Result<Response<'r>, Status> {
let mut builder = Response::build();
builder.header(ContentType::PNG);
let mut bytes = vec![];
try!(self.image
.write_to(&mut bytes, ImageFormat::PNG)
.map_err(|_| Status::InternalServerError));
builder.sized_body(Cursor::new(bytes));
builder.ok()
}
}
// Fetches the OTF data.
fn otf_data_from_request(face: &FontRequestFace) -> Result<Arc<Vec<u8>>, FontError> {
match *face {
FontRequestFace::Builtin(ref builtin_font_name) => {
// Read in the builtin font.
match BUILTIN_FONTS.iter().filter(|& &(name, _)| name == builtin_font_name).next() {
Some(&(_, path)) => {
let mut data = vec![];
File::open(path).expect("Couldn't find builtin font!")
.read_to_end(&mut data)
.expect("Couldn't read builtin font!");
Ok(Arc::new(data))
}
None => return Err(FontError::UnknownBuiltinFont),
}
}
FontRequestFace::Custom(ref encoded_data) => {
// Decode Base64-encoded OTF data.
let unsafe_otf_data = match base64::decode(encoded_data) {
Ok(unsafe_otf_data) => unsafe_otf_data,
Err(_) => return Err(FontError::Base64DecodingFailed),
};
// Sanitize.
match fontsan::process(&unsafe_otf_data) {
Ok(otf_data) => Ok(Arc::new(otf_data)),
Err(_) => return Err(FontError::FontSanitizationFailed),
}
}
}
}
// Fetches the SVG data.
#[cfg(feature = "reftests")]
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),
}
}
#[post("/partition-font", format = "application/json", data = "<request>")]
fn partition_font(request: Json<PartitionFontRequest>) -> Result<PartitionResponder, FontError> {
// Check the cache.
let cache_key = match request.face {
FontRequestFace::Builtin(ref builtin_font_name) => {
Some(MeshPackCacheKey {
builtin_font_name: (*builtin_font_name).clone(),
glyph_ids: request.glyphs.iter().map(|glyph| glyph.id).collect(),
})
}
_ => None,
};
if let Some(ref cache_key) = cache_key {
if let Ok(mut mesh_library_cache) = MESH_PACK_CACHE.lock() {
if let Some(cache_entry) = mesh_library_cache.get_mut(cache_key) {
return Ok((*cache_entry).clone())
}
}
}
// Parse glyph data.
let otf_data = try!(otf_data_from_request(&request.face));
let font = match Font::from_bytes(otf_data, request.font_index) {
Ok(font) => font,
Err(_) => return Err(FontError::FontLoadingFailed),
};
// Read glyph info.
let mut paths: Vec<Vec<PathEvent>> = vec![];
let mut path_descriptors = vec![];
for (glyph_index, glyph) in request.glyphs.iter().enumerate() {
// This might fail; if so, just leave it blank.
// FIXME(pcwalton): Should we add first-class support for transforms to `font-kit`?
let mut path_builder = lyon_path::default::Path::builder();
match font.outline(glyph.id, HintingOptions::None, &mut path_builder) {
Ok(()) => {
paths.push(Transform2DPathIter::new(path_builder.build().into_iter(),
&glyph.transform).collect())
}
Err(_) => paths.push(vec![]),
};
path_descriptors.push(PathDescriptor {
path_index: glyph_index,
fill_rule: FillRule::Winding,
})
}
// Partition the decoded glyph outlines.
let mut pack = MeshPack::new();
let path_partitioning_result = PathPartitioningResult::compute(&mut pack,
&path_descriptors,
&paths,
None,
false);
// Build the response.
let elapsed_ms = path_partitioning_result.elapsed_ms();
let responder = PartitionResponder {
data: path_partitioning_result.encoded_data,
time: elapsed_ms,
};
if let Some(cache_key) = cache_key {
if let Ok(mut mesh_library_cache) = MESH_PACK_CACHE.lock() {
mesh_library_cache.insert(cache_key, responder.clone());
}
}
Ok(responder)
}
#[post("/partition-svg-paths", format = "application/json", data = "<request>")]
fn partition_svg_paths(request: Json<PartitionSvgPathsRequest>)
-> Result<PartitionResponder, PartitionSvgPathsError> {
// Parse the SVG path.
//
// The client has already normalized it, so we only have to handle `M`, `L`, `C`, and `Z`
// commands.
let mut paths = vec![];
let mut path_descriptors = vec![];
let mut pack = MeshPack::new();
let mut path_index = 0;
for path in &request.paths {
let mut stream = vec![];
for segment in &path.segments {
match segment.kind {
'M' => {
stream.push(PathEvent::MoveTo(Point2D::new(segment.values[0] as f32,
segment.values[1] as f32)))
}
'L' => {
stream.push(PathEvent::LineTo(Point2D::new(segment.values[0] as f32,
segment.values[1] as f32)))
}
'C' => {
stream.push(PathEvent::CubicTo(Point2D::new(segment.values[0] as f32,
segment.values[1] as f32),
Point2D::new(segment.values[2] as f32,
segment.values[3] as f32),
Point2D::new(segment.values[4] as f32,
segment.values[5] as f32)))
}
'Z' => stream.push(PathEvent::Close),
_ => return Err(PartitionSvgPathsError::UnknownSvgPathCommandType),
}
}
let fill_rule = match path.kind {
PartitionSvgPathKind::Fill(fill_rule) => fill_rule.to_fill_rule(),
PartitionSvgPathKind::Stroke(_) => FillRule::Winding,
};
path_descriptors.push(PathDescriptor {
path_index: path_index,
fill_rule: fill_rule,
});
match path.kind {
PartitionSvgPathKind::Fill(_) => paths.push(stream),
PartitionSvgPathKind::Stroke(stroke_width) => {
let iterator = PathIter::new(stream.into_iter());
let stroke_style = StrokeStyle::new(stroke_width);
let path: Vec<_> = StrokeToFillIter::new(iterator, stroke_style).collect();
paths.push(path);
}
}
path_index += 1;
}
// Compute approximation tolerance.
let tolerance = f32::max(request.view_box_width, request.view_box_height) * 0.001;
// Partition the paths.
let path_partitioning_result = PathPartitioningResult::compute(&mut pack,
&path_descriptors,
&paths,
Some(tolerance),
false);
// Return the response.
let elapsed_ms = path_partitioning_result.elapsed_ms();
Ok(PartitionResponder {
data: path_partitioning_result.encoded_data,
time: elapsed_ms,
})
}
#[post("/render-reference/text", format = "application/json", data = "<request>")]
fn render_reference_text(request: Json<RenderTextReferenceRequest>)
-> Result<ReferenceImage, FontError> {
let otf_data = try!(otf_data_from_request(&request.face));
// Rasterize the glyph using the right rasterizer.
let mut canvas;
match request.renderer {
ReferenceTextRenderer::FreeType => {
let loader = match Font::from_bytes(otf_data, request.font_index) {
Ok(loader) => loader,
Err(_) => return Err(FontError::FontLoadingFailed),
};
let glyph_rect = try!(loader.raster_bounds(request.glyph,
request.point_size as f32,
&Point2D::zero(),
HintingOptions::None,
RasterizationOptions::SubpixelAa)
.map_err(|_| FontError::RasterizationFailed));
let glyph_dimensions = glyph_rect.size.to_u32();
canvas = Canvas::new(&glyph_dimensions, FontKitFormat::Rgba32);
let origin = Point2D::new(-glyph_rect.origin.x, -glyph_rect.origin.y).to_f32();
try!(loader.rasterize_glyph(&mut canvas,
request.glyph,
request.point_size as f32,
&origin,
HintingOptions::None,
RasterizationOptions::SubpixelAa)
.map_err(|_| FontError::RasterizationFailed));
}
#[cfg(target_os = "macos")]
ReferenceTextRenderer::CoreGraphics => {
let loader = match loaders::core_text::Font::from_bytes(otf_data, request.font_index) {
Ok(loader) => loader,
Err(_) => return Err(FontError::FontLoadingFailed),
};
let glyph_rect = try!(loader.raster_bounds(request.glyph,
request.point_size as f32,
&Point2D::zero(),
HintingOptions::None,
RasterizationOptions::SubpixelAa)
.map_err(|_| FontError::RasterizationFailed));
let glyph_dimensions = glyph_rect.size.to_u32();
canvas = Canvas::new(&glyph_dimensions, FontKitFormat::Rgba32);
let origin = Point2D::new(-glyph_rect.origin.x, -glyph_rect.origin.y).to_f32();
try!(loader.rasterize_glyph(&mut canvas,
request.glyph,
request.point_size as f32,
&origin,
HintingOptions::None,
RasterizationOptions::SubpixelAa)
.map_err(|_| FontError::RasterizationFailed));
}
};
let image_buffer = ImageBuffer::from_raw(canvas.size.width,
canvas.size.height,
canvas.pixels).unwrap();
let reference_image = ReferenceImage {
image: ImageRgba8(image_buffer),
};
Ok(reference_image)
}
#[cfg(feature = "reftests")]
#[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 mut image_data = (*surface.get_data().unwrap()).to_vec();
image_data.chunks_mut(4).for_each(|color| color.swap(0, 2));
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),
})
}
#[cfg(not(feature = "reftests"))]
#[post("/render-reference/svg", format = "application/json", data = "<request>")]
#[allow(unused_variables)]
fn render_reference_svg(request: Json<RenderSvgReferenceRequest>)
-> Result<ReferenceImage, SvgError> {
Err(SvgError::ReftestsDisabled)
}
// Static files
#[get("/")]
fn static_index() -> io::Result<NamedFile> {
NamedFile::open(STATIC_INDEX_PATH)
}
#[get("/demo/text")]
fn static_demo_text() -> io::Result<NamedFile> {
NamedFile::open(STATIC_TEXT_DEMO_PATH)
}
#[get("/demo/svg")]
fn static_demo_svg() -> io::Result<NamedFile> {
NamedFile::open(STATIC_SVG_DEMO_PATH)
}
#[get("/demo/3d")]
fn static_demo_3d() -> io::Result<NamedFile> {
NamedFile::open(STATIC_3D_DEMO_PATH)
}
#[get("/tools/benchmark")]
fn static_tools_benchmark() -> io::Result<NamedFile> {
NamedFile::open(STATIC_TOOLS_BENCHMARK_PATH)
}
#[get("/tools/reference-test")]
fn static_tools_reference_test() -> io::Result<NamedFile> {
NamedFile::open(STATIC_TOOLS_REFERENCE_TEST_PATH)
}
#[get("/tools/mesh-debugger")]
fn static_tools_mesh_debugger() -> io::Result<NamedFile> {
NamedFile::open(STATIC_TOOLS_MESH_DEBUGGER_PATH)
}
#[get("/doc/api")]
fn static_doc_api_index() -> Redirect {
Redirect::to(STATIC_DOC_API_INDEX_URI)
}
#[get("/doc/api/<file..>")]
fn static_doc_api(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(path::Path::new(STATIC_DOC_API_PATH).join(file)).ok()
}
#[get("/css/bootstrap/<file..>")]
fn static_css_bootstrap(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(path::Path::new(STATIC_CSS_BOOTSTRAP_PATH).join(file)).ok()
}
#[get("/css/<file>")]
fn static_css(file: String) -> Option<NamedFile> {
NamedFile::open(path::Path::new(STATIC_CSS_PATH).join(file)).ok()
}
#[get("/js/bootstrap/<file..>")]
fn static_js_bootstrap(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(path::Path::new(STATIC_JS_BOOTSTRAP_PATH).join(file)).ok()
}
#[get("/js/jquery/<file..>")]
fn static_js_jquery(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(path::Path::new(STATIC_JS_JQUERY_PATH).join(file)).ok()
}
#[get("/js/popper.js/<file..>")]
fn static_js_popper_js(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(path::Path::new(STATIC_JS_POPPER_JS_PATH).join(file)).ok()
}
#[get("/js/pathfinder/<file..>")]
fn static_js_pathfinder(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(path::Path::new(STATIC_JS_PATHFINDER_PATH).join(file)).ok()
}
#[get("/woff2/inter-ui/<file..>")]
fn static_woff2_inter_ui(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(path::Path::new(STATIC_WOFF2_INTER_UI_PATH).join(file)).ok()
}
#[get("/woff2/material-icons/<file..>")]
fn static_woff2_material_icons(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(path::Path::new(STATIC_WOFF2_MATERIAL_ICONS_PATH).join(file)).ok()
}
#[get("/glsl/<file..>")]
fn static_glsl(file: PathBuf) -> Option<Shader> {
Shader::open(path::Path::new(STATIC_GLSL_PATH).join(file)).ok()
}
#[get("/otf/demo/<font_name>")]
fn static_otf_demo(font_name: String) -> Option<NamedFile> {
BUILTIN_FONTS.iter()
.filter(|& &(name, _)| name == font_name)
.next()
.and_then(|&(_, path)| NamedFile::open(path::Path::new(path)).ok())
}
#[get("/svg/demo/<svg_name>")]
fn static_svg_demo(svg_name: String) -> Option<NamedFile> {
BUILTIN_SVGS.iter()
.filter(|& &(name, _)| name == svg_name)
.next()
.and_then(|&(_, path)| NamedFile::open(path::Path::new(path)).ok())
}
#[get("/data/<file..>")]
fn static_data(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(path::Path::new(STATIC_DATA_PATH).join(file)).ok()
}
#[get("/test-data/<file..>")]
fn static_test_data(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(path::Path::new(STATIC_TEST_DATA_PATH).join(file)).ok()
}
#[get("/textures/<file..>")]
fn static_textures(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(path::Path::new(STATIC_TEXTURES_PATH).join(file)).ok()
}
struct Shader {
file: File,
}
impl Shader {
fn open(path: PathBuf) -> io::Result<Shader> {
File::open(path).map(|file| Shader {
file: file,
})
}
}
impl<'a> Responder<'a> for Shader {
fn respond_to(self, _: &Request) -> Result<Response<'a>, Status> {
Response::build().header(ContentType::Plain).streamed_body(self.file).ok()
}
}
fn main() {
drop(env_logger::init());
let rocket = rocket::ignite();
match rocket.config().limits.get("json") {
Some(size) if size >= SUGGESTED_JSON_SIZE_LIMIT => {}
None | Some(_) => {
eprintln!("warning: the JSON size limit is small; many SVGs will not upload properly");
eprintln!("warning: adding the following to `Rocket.toml` is suggested:");
eprintln!("warning: [development]");
eprintln!("warning: limits = {{ json = 33554432 }}");
}
}
rocket.mount("/", routes![
partition_font,
partition_svg_paths,
render_reference_text,
render_reference_svg,
static_index,
static_demo_text,
static_demo_svg,
static_demo_3d,
static_tools_benchmark,
static_tools_reference_test,
static_tools_mesh_debugger,
static_doc_api_index,
static_doc_api,
static_css,
static_css_bootstrap,
static_js_bootstrap,
static_js_jquery,
static_js_popper_js,
static_js_pathfinder,
static_woff2_inter_ui,
static_woff2_material_icons,
static_glsl,
static_otf_demo,
static_svg_demo,
static_data,
static_test_data,
static_textures,
]).launch();
}
mod tiling {
use euclid::{Point2D, Rect, Size2D, Vector2D};
use lyon_path::PathEvent;
use pathfinder_geometry::clip::RectClipper;
use std::f32;
use std::mem;
const TILE_SIZE: f32 = 8.0;
pub fn tile_path(path: &[PathEvent]) -> Vec<TileInfo> {
let mut tiles = vec![];
let mut all_points = vec![];
for event in path {
match *event {
PathEvent::MoveTo(point) | PathEvent::LineTo(point) => all_points.push(point),
PathEvent::QuadraticTo(point0, point1) => {
all_points.extend_from_slice(&[point0, point1])
}
PathEvent::CubicTo(point0, point1, point2) => {
all_points.extend_from_slice(&[point0, point1, point2])
}
PathEvent::Arc(..) | PathEvent::Close => {}
}
}
let bounding_rect = Rect::from_points(all_points);
//eprintln!("path {}: bounding rect = {:?}", path_index, bounding_rect);
let tile_size = Size2D::new(TILE_SIZE, TILE_SIZE);
let (mut full_tile_count, mut tile_count) = (0, 0);
let mut y = bounding_rect.origin.y - bounding_rect.origin.y % TILE_SIZE;
loop {
let mut x = bounding_rect.origin.x - bounding_rect.origin.x % TILE_SIZE;
loop {
let origin = Point2D::new(x, y);
let clipper = RectClipper::new(&Rect::new(origin, tile_size), path);
let mut tile_path = clipper.clip();
simplify_path(&mut tile_path);
translate_path(&mut tile_path, &Vector2D::new(-x, -y));
//eprintln!("({},{}): {:?}", x, y, tile_path);
if !tile_path.is_empty() {
tiles.push(TileInfo {
events: tile_path,
origin,
});
full_tile_count += 1;
} else {
tile_count += 1;
}
if x < bounding_rect.max_x() {
x += TILE_SIZE;
} else {
break
}
}
if y < bounding_rect.max_y() {
y += TILE_SIZE;
} else {
break
}
}
tiles
}
fn simplify_path(output: &mut Vec<PathEvent>) {
let mut subpath = vec![];
for event in mem::replace(output, vec![]) {
subpath.push(event);
if let PathEvent::Close = event {
if subpath.len() > 2 {
output.extend_from_slice(&subpath);
}
subpath.clear();
}
}
}
fn translate_path(output: &mut [PathEvent], vector: &Vector2D<f32>) {
for event in output {
match *event {
PathEvent::Close => {}
PathEvent::MoveTo(ref mut to) | PathEvent::LineTo(ref mut to) => *to += *vector,
PathEvent::QuadraticTo(ref mut cp, ref mut to) => {
*cp += *vector;
*to += *vector;
}
PathEvent::CubicTo(ref mut cp0, ref mut cp1, ref mut to) => {
*cp0 += *vector;
*cp1 += *vector;
*to += *vector;
}
PathEvent::Arc(ref mut center, _, _, _) => *center += *vector,
}
}
}
#[derive(Debug)]
pub struct TileInfo {
pub events: Vec<PathEvent>,
pub origin: Point2D<f32>,
}
}

View File

@ -21,11 +21,10 @@ extern crate rand;
use arrayvec::ArrayVec; use arrayvec::ArrayVec;
use byteorder::{LittleEndian, WriteBytesExt}; use byteorder::{LittleEndian, WriteBytesExt};
use clap::{App, Arg}; use clap::{App, Arg};
use euclid::{Point2D, Rect, Size2D, Transform2D}; use euclid::{Point2D, Rect, Size2D};
use fixedbitset::FixedBitSet; use fixedbitset::FixedBitSet;
use hashbrown::HashMap; use hashbrown::HashMap;
use jemallocator; use jemallocator;
use lyon_geom::{CubicBezierSegment, QuadraticBezierSegment};
use lyon_path::PathEvent; use lyon_path::PathEvent;
use lyon_path::iterator::PathIter; use lyon_path::iterator::PathIter;
use pathfinder_geometry::stroke::{StrokeStyle, StrokeToFillIter}; use pathfinder_geometry::stroke::{StrokeStyle, StrokeToFillIter};
@ -451,23 +450,23 @@ impl Contour {
fn segment_after(&self, point_index: u32) -> Segment { fn segment_after(&self, point_index: u32) -> Segment {
debug_assert!(self.point_is_endpoint(point_index)); debug_assert!(self.point_is_endpoint(point_index));
let mut segment = Segment::new(); let mut segment = Segment::none();
segment.flags |= SegmentFlags::HAS_ENDPOINTS;
segment.baseline.set_from(&self.position_of(point_index)); segment.baseline.set_from(&self.position_of(point_index));
let point1_index = self.add_to_point_index(point_index, 1); let point1_index = self.add_to_point_index(point_index, 1);
if self.point_is_endpoint(point1_index) { if self.point_is_endpoint(point1_index) {
segment.baseline.set_to(&self.position_of(point1_index)); segment.baseline.set_to(&self.position_of(point1_index));
segment.kind = SegmentKind::Line;
} else { } else {
segment.ctrl.set_from(&self.position_of(point1_index)); segment.ctrl.set_from(&self.position_of(point1_index));
segment.flags |= SegmentFlags::HAS_CONTROL_POINT_0;
let point2_index = self.add_to_point_index(point_index, 2); let point2_index = self.add_to_point_index(point_index, 2);
if self.point_is_endpoint(point2_index) { if self.point_is_endpoint(point2_index) {
segment.baseline.set_to(&self.position_of(point2_index)); segment.baseline.set_to(&self.position_of(point2_index));
segment.kind = SegmentKind::Quadratic;
} else { } else {
segment.ctrl.set_to(&self.position_of(point2_index)); segment.ctrl.set_to(&self.position_of(point2_index));
segment.flags |= SegmentFlags::HAS_CONTROL_POINT_1; segment.kind = SegmentKind::Cubic;
let point3_index = self.add_to_point_index(point_index, 3); let point3_index = self.add_to_point_index(point_index, 3);
segment.baseline.set_to(&self.position_of(point3_index)); segment.baseline.set_to(&self.position_of(point3_index));
@ -655,15 +654,15 @@ impl<'a> Iterator for ContourIter<'a> {
struct Segment { struct Segment {
baseline: LineSegmentF32, baseline: LineSegmentF32,
ctrl: LineSegmentF32, ctrl: LineSegmentF32,
flags: SegmentFlags, kind: SegmentKind,
} }
impl Segment { impl Segment {
fn new() -> Segment { fn none() -> Segment {
Segment { Segment {
baseline: LineSegmentF32::default(), baseline: LineSegmentF32::default(),
ctrl: LineSegmentF32::default(), ctrl: LineSegmentF32::default(),
flags: SegmentFlags::empty(), kind: SegmentKind::None,
} }
} }
@ -671,31 +670,19 @@ impl Segment {
Segment { Segment {
baseline: *line, baseline: *line,
ctrl: LineSegmentF32::default(), ctrl: LineSegmentF32::default(),
flags: SegmentFlags::HAS_ENDPOINTS, kind: SegmentKind::Line,
} }
} }
fn as_line_segment(&self) -> Option<LineSegmentF32> { fn as_line_segment(&self) -> LineSegmentF32 {
if !self.flags.contains(SegmentFlags::HAS_CONTROL_POINT_0) { debug_assert!(self.is_line_segment());
Some(self.baseline) self.baseline
} else {
None
}
} }
fn is_none(&self) -> bool { fn is_none(&self) -> bool { self.kind == SegmentKind::None }
!self.flags.contains(SegmentFlags::HAS_ENDPOINTS) fn is_line_segment(&self) -> bool { self.kind == SegmentKind::Line }
} fn is_quadratic_segment(&self) -> bool { self.kind == SegmentKind::Quadratic }
fn is_cubic_segment(&self) -> bool { self.kind == SegmentKind::Cubic }
fn is_line_segment(&self) -> bool {
self.flags.contains(SegmentFlags::HAS_ENDPOINTS) &&
!self.flags.contains(SegmentFlags::HAS_CONTROL_POINT_0 |
SegmentFlags::HAS_CONTROL_POINT_1)
}
fn is_cubic_segment(&self) -> bool {
self.flags.contains(SegmentFlags::HAS_CONTROL_POINT_0 | SegmentFlags::HAS_CONTROL_POINT_1)
}
fn as_cubic_segment(&self) -> CubicSegment { fn as_cubic_segment(&self) -> CubicSegment {
debug_assert!(self.is_cubic_segment()); debug_assert!(self.is_cubic_segment());
@ -719,12 +706,8 @@ impl Segment {
fn reversed(&self) -> Segment { fn reversed(&self) -> Segment {
Segment { Segment {
baseline: self.baseline.reversed(), baseline: self.baseline.reversed(),
ctrl: if !self.flags.contains(SegmentFlags::HAS_CONTROL_POINT_1) { ctrl: if self.is_quadratic_segment() { self.ctrl } else { self.ctrl.reversed() },
self.ctrl kind: self.kind,
} else {
self.ctrl.reversed()
},
flags: self.flags,
} }
} }
@ -739,12 +722,12 @@ impl Segment {
} }
} }
bitflags! { #[derive(Clone, Copy, Debug, PartialEq)]
struct SegmentFlags: u8 { enum SegmentKind {
const HAS_ENDPOINTS = 0x01; None,
const HAS_CONTROL_POINT_0 = 0x02; Line,
const HAS_CONTROL_POINT_1 = 0x04; Quadratic,
} Cubic,
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
@ -816,17 +799,14 @@ impl<'s> CubicSegment<'s> {
let baseline1 = assemble(&p0123, &p0p3, 0, 1); let baseline1 = assemble(&p0123, &p0p3, 0, 1);
let ctrl1 = assemble(&p012p123, &p12p23, 1, 1); let ctrl1 = assemble(&p012p123, &p12p23, 1, 1);
let flags = SegmentFlags::HAS_ENDPOINTS | SegmentFlags::HAS_CONTROL_POINT_0 |
SegmentFlags::HAS_CONTROL_POINT_1;
return (Segment { return (Segment {
baseline: LineSegmentF32(baseline0), baseline: LineSegmentF32(baseline0),
ctrl: LineSegmentF32(ctrl0), ctrl: LineSegmentF32(ctrl0),
flags, kind: SegmentKind::Cubic,
}, Segment { }, Segment {
baseline: LineSegmentF32(baseline1), baseline: LineSegmentF32(baseline1),
ctrl: LineSegmentF32(ctrl1), ctrl: LineSegmentF32(ctrl1),
flags, kind: SegmentKind::Cubic,
}) })
} }
@ -1714,12 +1694,6 @@ fn usvg_rect_to_euclid_rect(rect: &UsvgRect) -> Rect<f32> {
Rect::new(Point2D::new(rect.x, rect.y), Size2D::new(rect.width, rect.height)).to_f32() Rect::new(Point2D::new(rect.x, rect.y), Size2D::new(rect.width, rect.height)).to_f32()
} }
fn usvg_transform_to_euclid_transform_2d(transform: &UsvgTransform) -> Transform2D<f32> {
Transform2D::row_major(transform.a as f32, transform.b as f32,
transform.c as f32, transform.d as f32,
transform.e as f32, transform.f as f32)
}
fn usvg_transform_to_transform_2d(transform: &UsvgTransform) -> Transform2DF32 { fn usvg_transform_to_transform_2d(transform: &UsvgTransform) -> Transform2DF32 {
Transform2DF32::row_major(transform.a as f32, transform.b as f32, Transform2DF32::row_major(transform.a as f32, transform.b as f32,
transform.c as f32, transform.d as f32, transform.c as f32, transform.d as f32,
@ -1849,23 +1823,22 @@ impl<I> Iterator for MonotonicConversionIter<I> where I: Iterator<Item = PathEve
Some(PathEvent::LineTo(to)) Some(PathEvent::LineTo(to))
} }
PathEvent::CubicTo(ctrl0, ctrl1, to) => { PathEvent::CubicTo(ctrl0, ctrl1, to) => {
let mut segment = Segment::new(); let mut segment = Segment::none();
segment.baseline = LineSegmentF32::new(&Point2DF32::from_euclid(self.last_point), segment.baseline = LineSegmentF32::new(&Point2DF32::from_euclid(self.last_point),
&Point2DF32::from_euclid(to)); &Point2DF32::from_euclid(to));
segment.ctrl = LineSegmentF32::new(&Point2DF32::from_euclid(ctrl0), segment.ctrl = LineSegmentF32::new(&Point2DF32::from_euclid(ctrl0),
&Point2DF32::from_euclid(ctrl1)); &Point2DF32::from_euclid(ctrl1));
segment.flags = SegmentFlags::HAS_ENDPOINTS | SegmentFlags::HAS_CONTROL_POINT_0 | segment.kind = SegmentKind::Cubic;
SegmentFlags::HAS_CONTROL_POINT_1;
return self.handle_cubic(&segment); return self.handle_cubic(&segment);
} }
PathEvent::QuadraticTo(ctrl, to) => { PathEvent::QuadraticTo(ctrl, to) => {
// TODO(pcwalton): Don't degree elevate! // TODO(pcwalton): Don't degree elevate!
let mut segment = Segment::new(); let mut segment = Segment::none();
segment.baseline = LineSegmentF32::new(&Point2DF32::from_euclid(self.last_point), segment.baseline = LineSegmentF32::new(&Point2DF32::from_euclid(self.last_point),
&Point2DF32::from_euclid(to)); &Point2DF32::from_euclid(to));
segment.ctrl = LineSegmentF32::new(&Point2DF32::from_euclid(ctrl), segment.ctrl = LineSegmentF32::new(&Point2DF32::from_euclid(ctrl),
&Point2DF32::default()); &Point2DF32::default());
segment.flags = SegmentFlags::HAS_ENDPOINTS | SegmentFlags::HAS_CONTROL_POINT_0; segment.kind = SegmentKind::Quadratic;
return self.handle_cubic(&segment.to_cubic()); return self.handle_cubic(&segment.to_cubic());
} }
PathEvent::Close => Some(PathEvent::Close), PathEvent::Close => Some(PathEvent::Close),
@ -2002,10 +1975,10 @@ impl ActiveEdge {
let winding = segment.baseline.y_winding(); let winding = segment.baseline.y_winding();
if segment.is_line_segment() { if segment.is_line_segment() {
let line_segment = segment.as_line_segment().unwrap(); let line_segment = segment.as_line_segment();
self.segment = match self.process_line_segment(&line_segment, built_object, tile_y) { self.segment = match self.process_line_segment(&line_segment, built_object, tile_y) {
Some(lower_part) => Segment::from_line(&lower_part), Some(lower_part) => Segment::from_line(&lower_part),
None => Segment::new(), None => Segment::none(),
}; };
return; return;
} }
@ -2033,7 +2006,7 @@ impl ActiveEdge {
built_object, built_object,
tile_y) { tile_y) {
Some(ref lower_part) => Segment::from_line(lower_part), Some(ref lower_part) => Segment::from_line(lower_part),
None => Segment::new(), None => Segment::none(),
}; };
return; return;
} }
@ -2187,44 +2160,13 @@ impl LineSegmentF32 {
fn to_x(&self) -> f32 { self.0[2] } fn to_x(&self) -> f32 { self.0[2] }
fn to_y(&self) -> f32 { self.0[3] } fn to_y(&self) -> f32 { self.0[3] }
fn min(&self, max: &Point2DF32) -> LineSegmentF32 {
unsafe {
let max_max = Sse41::castpd_ps(Sse41::unpacklo_pd(Sse41::castps_pd(max.0),
Sse41::castps_pd(max.0)));
LineSegmentF32(Sse41::min_ps(max_max, self.0))
}
}
fn clamp(&self, min: &Point2DF32, max: &Point2DF32) -> LineSegmentF32 {
unsafe {
let min_min = Sse41::castpd_ps(Sse41::unpacklo_pd(Sse41::castps_pd(min.0),
Sse41::castps_pd(min.0)));
let max_max = Sse41::castpd_ps(Sse41::unpacklo_pd(Sse41::castps_pd(max.0),
Sse41::castps_pd(max.0)));
LineSegmentF32(Sse41::min_ps(max_max, Sse41::max_ps(min_min, self.0)))
}
}
fn scale(&self, factor: f32) -> LineSegmentF32 { fn scale(&self, factor: f32) -> LineSegmentF32 {
unsafe { unsafe {
LineSegmentF32(Sse41::mul_ps(self.0, Sse41::set1_ps(factor))) LineSegmentF32(Sse41::mul_ps(self.0, Sse41::set1_ps(factor)))
} }
} }
fn floor(&self) -> LineSegmentF32 {
unsafe {
LineSegmentF32(Sse41::fastfloor_ps(self.0))
}
}
fn fract(&self) -> LineSegmentF32 {
unsafe {
LineSegmentF32(Sse41::sub_ps(self.0, self.floor().0))
}
}
fn split(&self, t: f32) -> (LineSegmentF32, LineSegmentF32) { fn split(&self, t: f32) -> (LineSegmentF32, LineSegmentF32) {
//println!("LineSegmentF32::split(t={})", t);
debug_assert!(t >= 0.0 && t <= 1.0); debug_assert!(t >= 0.0 && t <= 1.0);
unsafe { unsafe {
let from_from = Sse41::castpd_ps(Sse41::unpacklo_pd(Sse41::castps_pd(self.0), let from_from = Sse41::castpd_ps(Sse41::unpacklo_pd(Sse41::castps_pd(self.0),
@ -2250,23 +2192,6 @@ impl LineSegmentF32 {
} }
} }
fn to_line_segment_u4(&self) -> LineSegmentU4 {
unsafe {
let values = Sse41::cvtps_epi32(Sse41::fastfloor_ps(self.0));
let mask = Sse41::set1_epi32(0x0c04_0800);
let values_0213 = Sse41::shuffle_epi8(values, mask)[0] as u32;
LineSegmentU4((values_0213 | (values_0213 >> 12)) as u16)
}
}
fn to_line_segment_u8(&self) -> LineSegmentU8 {
unsafe {
let values = Sse41::cvtps_epi32(Sse41::fastfloor_ps(self.0));
let mask = Sse41::set1_epi32(0x0c08_0400);
LineSegmentU8(Sse41::shuffle_epi8(values, mask)[0] as u32)
}
}
fn solve_t_for_x(&self, x: f32) -> f32 { fn solve_t_for_x(&self, x: f32) -> f32 {
(x - self.from_x()) / (self.to_x() - self.from_x()) (x - self.from_x()) / (self.to_x() - self.from_x())
} }
@ -2414,25 +2339,6 @@ impl Transform2DF32 {
} }
} }
// Path utilities
const TINY_EPSILON: f32 = 0.1;
fn cubic_segment_is_tiny(segment: &CubicBezierSegment<f32>) -> bool {
let (x0, x1) = segment.fast_bounding_range_x();
let (y0, y1) = segment.fast_bounding_range_y();
let (x_delta, y_delta) = (f32::abs(x0 - x1), f32::abs(y0 - y1));
x_delta < TINY_EPSILON || y_delta < TINY_EPSILON
}
fn quadratic_segment_is_tiny(segment: &QuadraticBezierSegment<f32>) -> bool {
let (x0, x1) = segment.fast_bounding_range_x();
let (y0, y1) = segment.fast_bounding_range_y();
let (x_delta, y_delta) = (f32::abs(x0 - x1), f32::abs(y0 - y1));
x_delta < TINY_EPSILON || y_delta < TINY_EPSILON
}
// SIMD extensions // SIMD extensions
trait SimdExt: Simd { trait SimdExt: Simd {
@ -2457,12 +2363,6 @@ fn alignup_i32(a: i32, b: i32) -> i32 {
(a + b - 1) / b (a + b - 1) / b
} }
fn t_is_too_close_to_zero_or_one(t: f32) -> bool {
const EPSILON: f32 = 0.001;
t < EPSILON || t > 1.0 - EPSILON
}
// Testing // Testing
#[cfg(test)] #[cfg(test)]