Remove old demo server; remove `SegmentFlags`
This commit is contained in:
parent
7bc62bb5af
commit
3165a4a1b0
File diff suppressed because it is too large
Load Diff
|
@ -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" }
|
|
||||||
|
|
|
@ -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" }
|
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"lockfileVersion": 1
|
|
||||||
}
|
|
|
@ -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>,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)]
|
||||||
|
|
Loading…
Reference in New Issue