From 3b0b8558e56c85c727dfb2f06c6958b94a63b60f Mon Sep 17 00:00:00 2001 From: MeiHui FAN Date: Thu, 28 Sep 2023 11:46:45 +0800 Subject: [PATCH] support to compile ftgrays/ftraster/gpac-evg preparing for new renderer implementation --- .vscode/settings.json | 5 ++ Cargo.toml | 18 ++++++ build.rs | 67 ++++++++++++++++++++-- src/lib.rs | 11 ++++ src/render.rs | 7 +++ tests/raster.rs | 125 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 229 insertions(+), 4 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 tests/raster.rs diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..3ef992a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "C_Cpp.default.defines": [ "STANDALONE_", + "FALL_THROUGH=((void)0)", "FT_BEGIN_HEADER" ], + "C_Cpp.default.includePath": [ "src/evg", "src/raster" ] +} diff --git a/Cargo.toml b/Cargo.toml index fe4e828..5d56575 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,12 +22,30 @@ usvg = "0.36" kurbo = "0.10" # Bezier curves utils #build-time = { version = "0.1", git = "https://github.com/AlephAlpha/build-time" } +png = { version = "0.17", optional = true } + +[lib] +# Disable doctests as a workaround for https://github.com/rust-lang/rust-bindgen/issues/1313 +#doctest = false + +[features] +#default = [ "cc" ] +cc = [ "dep:cc", "dep:bindgen", "dep:glob", "dep:png" ] # implied by optional dependency [dev-dependencies] #rexpect = "0.5" [build-dependencies] +cc = { version = "1.0", optional = true } +bindgen = { version = "0.68", optional = true } +glob = { version = "0.3", optional = true } chrono = "0.4" +[profile.release] +codegen-units = 1 +strip = 'debuginfo' +panic = 'abort' +lto = 'fat' # true + [workspace] diff --git a/build.rs b/build.rs index 8ad21b6..2ea3d26 100644 --- a/build.rs +++ b/build.rs @@ -1,16 +1,75 @@ -fn main() { // https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html +// https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html +fn main() -> Result<(), Box> { + //println!("cargo:rerun-if-changed=build.rs"); // XXX: prevent re-run indead // By default, cargo always re-run the build script if any file within the package // is changed, and no any rerun-if instruction is emitted. println!("cargo:rerun-if-changed=src/"); println!("cargo:rerun-if-changed=.git/index"); let output = std::process::Command::new("git") - .args(["rev-parse", "--short", "HEAD"]).output().unwrap(); - println!("cargo:rustc-env=BUILD_GIT_HASH={}", String::from_utf8(output.stdout).unwrap()); + .args(["rev-parse", "--short", "HEAD"]).output()?; + println!("cargo:rustc-env=BUILD_GIT_HASH={}", String::from_utf8(output.stdout)?); println!("cargo:rustc-env=BUILD_TIMESTAMP={}", chrono::Local::now().format("%H:%M:%S%z %Y-%m-%d")); - //println!("cargo:rerun-if-changed=build.rs"); // XXX: prevent re-run indead + #[cfg(feature = "cc")] { use std::path::PathBuf; + #[derive(Debug)] struct DoctestComment; + impl bindgen::callbacks::ParseCallbacks for DoctestComment { + fn process_comment(&self, comment: &str) -> Option { + Some(format!("````c,ignore\n{comment}\n````")) // FIXME: + } + } + + let module = "ftgrays"; + cc::Build::new().flag("-std=c99").flag("-pedantic") + .define("FALL_THROUGH", "((void)0)").file("src/raster/ftgrays.c") + .opt_level(3).define("NDEBUG", None).file("src/raster/ftraster.c") + .files(glob::glob("src/raster/stroke/*.c")?.filter_map(Result::ok)) + //.flag("-Wno-unused-variable").flag("-Wno-unused-function") + .define("STANDALONE_", None).compile(module); + + // The bindgen::Builder is the main entry point to bindgen, + // and lets you build up options for the resulting bindings. + bindgen::Builder::default().header("src/raster/ftmisc.h") + .clang_arg(r"-DFT_CONFIG_STANDARD_LIBRARY_H=") + .clang_arg("-DSTANDALONE_").header("src/raster/ftgrays.h") + .clang_arg("-DFT_BEGIN_HEADER=").clang_arg("-DFT_END_HEADER=") + .allowlist_item("FT_OUTLINE_.*|FT_RASTER_FLAG_.*|FT_CURVE_TAG.*") + .layout_tests(false).derive_copy(false).allowlist_var("ft_standard_raster") + .allowlist_var("ft_grays_raster").allowlist_type("FT_Outline|FT_Pixel_Mode") + .default_enum_style(bindgen::EnumVariation::Rust { non_exhaustive: true }) + .default_macro_constant_type(bindgen::MacroTypeVariation::Signed) + .parse_callbacks(Box::new(DoctestComment)).generate_comments(false) // XXX: + // Tell cargo to invalidate the built crate whenever any of the + // included header files changed. + .parse_callbacks(Box::new(bindgen::CargoCallbacks)).generate()? + .write_to_file(PathBuf::from(std::env::var("OUT_DIR")?).join(format!("{module}.rs")))?; + + /* ln -s /path/to/freetype2/{include/freetype/ftimage.h,src/smooth/ftgrays.[ch],\ + src/raster/{ftmisc.h,ftraster.c}} src/raster/ # ftstroke.c,fttrigon.c + * ln -s /path/to/gpac/src/evg/{ftgrays.c,rast_soft.h,stencil.c,surface.c,\ + raster_{argb,rgb,565}.c} src/evg/ + * ln -s /path/to/gpac/include/gpac/{evg,color,constants,maths,path2d,setup,\ + tools,thread}.h src/evg/ + * touch src/evg/gpac/{Remotery,config_file,configuration,main,module,version}.h + */ + let module = "gpac_evg"; + cc::Build::new().flag("-std=c17").flag("-Isrc/evg").define("GPAC_FIXED_POINT", None) + .flag("-Wno-unused-parameter").define("GPAC_DISABLE_THREADS", None) + .flag("-Wno-pointer-sign").define("GPAC_DISABLE_LOG", None) + .files(glob::glob("src/evg/*.c")?.filter_map(Result::ok)) + .opt_level(3).define("NDEBUG", None).compile(module); + + bindgen::Builder::default().header("src/evg/gpac/evg.h").clang_arg("-Isrc/evg") + .default_enum_style(bindgen::EnumVariation::Rust { non_exhaustive: true }) + .clang_arg("-DGPAC_DISABLE_THREADS").allowlist_function("gf_evg_s.*") + .layout_tests(false).derive_copy(false).allowlist_item("GF_LINE_.*") + .clang_arg("-DGPAC_FIXED_POINT").allowlist_function("gf_path_.*") + .parse_callbacks(Box::new(bindgen::CargoCallbacks)).generate()? + .write_to_file(PathBuf::from(std::env::var("OUT_DIR")?).join(format!("{module}.rs")))?; + } + + Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 7e19ac2..e50651e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,15 @@ +#![allow(non_snake_case)] #![allow(non_camel_case_types)] #![allow(non_upper_case_globals)] +#![allow(clippy::approx_constant)] #![allow(clippy::useless_transmute)] +#[cfg(feature = "cc")] pub mod ftgrays { + include!(concat!(env!("OUT_DIR"), "/ftgrays.rs")); + + impl From<(FT_Pos, FT_Pos)> for FT_Vector { + fn from(v: (FT_Pos, FT_Pos)) -> Self { Self { x: v.0, y: v.1 } } + } +} +#[cfg(feature = "cc")] pub mod gpac_evg { include!(concat!(env!("OUT_DIR"), "/gpac_evg.rs")); } + pub mod tinyvg; pub mod render; pub mod convert; diff --git a/src/render.rs b/src/render.rs index 0397229..cb4bbdb 100644 --- a/src/render.rs +++ b/src/render.rs @@ -3,6 +3,13 @@ use crate::tinyvg::*; use tiny_skia as skia; use std::result::Result; +/* TODO: https://github.com/sammycage/plutovg + https://github.com/gpac/gpac/tree/master/src/evg/ + https://github.com/kiba/SDL_svg/blob/master/ftgrays.c + https://github.com/Samsung/rlottie/tree/master/src/vector/freetype + https://gitlab.freedesktop.org/freetype/freetype/-/blob/master/src/smooth/ftgrays.c + https://gitlab.freedesktop.org/freetype/freetype/-/blob/master/src/raster/ftraster.c */ + pub trait Render { fn render(&self, scale: f32) -> Result; } impl Render for TVGImage { // TODO: to render with femtovg? diff --git a/tests/raster.rs b/tests/raster.rs new file mode 100644 index 0000000..c5a1b9e --- /dev/null +++ b/tests/raster.rs @@ -0,0 +1,125 @@ + +use core::{ptr::null_mut, ffi::c_void}; + +#[cfg(feature = "cc")] +#[test] fn raster_evg() -> Result<(), Box> { + use intvg::gpac_evg::*; + + unsafe { + let surf = gf_evg_surface_new(Bool::GF_FALSE); + + //let _err = gf_evg_surface_attach_to_buffer(surf, pixels, width, height, + // pitch_x, pitch_y, GF_PixelFormat::GF_PIXEL_RGB); + //let _err = gf_evg_surface_viewport(surf, x, y, w, h); + //let _err = gf_evg_surface_clear(surf, rc, 0xAARRGGBB); + + let path = gf_path_new(); + + //gf_path_add_move_to(path, x, y); + //gf_path_add_line_to(path, x, y); + //gf_path_add_bezier(path, pts, nb_pts); + //gf_path_add_cubic_to(path, c1_x, c1_y, c2_x, c2_y, x, y); + //gf_path_add_quadratic_to(path, c_x, c_y, x, y); + //gf_path_add_rect(path, ox, oy, w, h); + //gf_path_add_ellipse(path, cx, cy, a_axis, b_axis); + //gf_path_add_svg_arc_to(path, end_x, end_y, r_x, r_y, x_rotation, large_arc, sweep); + + let _err = gf_path_close(path); + + let pen = GF_PenSettings { + width: 0, path_length: 0, cap: 1, join: 1, // XXX: + align: 0, dash: 0, miterLimit: 0, dash_offset: 0, dash_set: null_mut(), + }; + let gpol = gf_path_get_outline(path, pen); + + let stencil = gf_evg_stencil_new(GF_StencilType::GF_STENCIL_SOLID); + + //let _err = gf_evg_stencil_set_brush_color(stencil, 0xAARRGGBB); + //let _err = gf_evg_stencil_set_gradient_mode(stencil, + // GF_GradientMode::GF_GRADIENT_MODE_PAD); + //let _err = gf_evg_stencil_set_linear_gradient(stencil, start_x, start_y, end_x, end_y); + //let _err = gf_evg_stencil_set_radial_gradient(stencil, cx, cy, fx, fy, x_radius, y_radius); + //let _err = gf_evg_stencil_set_gradient_interpolation(stencil, pos, col, count); + //let _err = gf_evg_stencil_set_alpha(stencil, alpha); + + let _err = gf_evg_surface_set_path(surf, gpol); + let _err = gf_evg_surface_fill(surf, stencil); + + let _err = gf_evg_surface_set_path(surf, path); + let _err = gf_evg_surface_fill(surf, stencil); + + gf_path_del(gpol); + gf_path_del(path); + gf_evg_surface_delete(surf); + gf_evg_stencil_delete(stencil); + } + + Ok(()) +} + +#[cfg(feature = "cc")] +#[test] fn raster_ftgrays() -> Result<(), Box> { + use intvg::ftgrays::*; + + let mut points = vec![ // FTDemo_Icon @freetype2/demo/src/ftcommon.c + ( 4, 8).into(), ( 4,10).into(), ( 8,12).into(), ( 8,52).into(), ( 4,54).into(), + ( 4,56).into(), (60,56).into(), (60,44).into(), (58,44).into(), (56,52).into(), + (44,52).into(), (44,12).into(), (48,10).into(), (48, 8).into(), (32, 8).into(), + (32,10).into(), (36,12).into(), (36,52).into(), (16,52).into(), (16,36).into(), + (24,36).into(), (26,40).into(), (28,40).into(), (28,28).into(), (26,28).into(), + (24,32).into(), (16,32).into(), (16,12).into(), (20,10).into(), (20, 8).into(), ]; + let mut tags = vec![FT_CURVE_TAG_ON as i8; points.len()]; + let mut contours = vec![29]; + + let mut bitmap = FT_Bitmap { + rows: 0, width: 0, pitch: 0, buffer: null_mut(), // FIXME: + + pixel_mode: FT_Pixel_Mode::FT_PIXEL_MODE_LCD as u8, // FT_PIXEL_MODE_BGRA + // https://docs.rs/bindgen/latest/bindgen/struct.Builder.html#enums + // Not used currently, or only used with FT_PIXEL_MODE_GRAY + palette_mode: 0, palette: null_mut(), num_grays: 0, + }; + + #[allow(unused)] + unsafe extern "C" fn span_func(y: i32, count: i32, // TODO: solid/linear/radial/stroke + spans: *const FT_Span, user: *mut c_void) { // The gray span drawing callback. + let bitmap = user as *mut FT_Bitmap; assert!(0 < count); + + for span in core::slice::from_raw_parts(spans, count as usize) { + for x in span.x..span.len as i16 { + // TODO: handle pixel at (x, y) with span.coverage (alpha) + } + } + } + + let outline = FT_Outline { + n_points: points.len() as i16, points: points.as_mut_ptr(), tags: tags.as_mut_ptr(), + n_contours: contours.len() as i16, contours: contours.as_mut_ptr(), + flags: FT_OUTLINE_NONE, // XXX: FT_OUTLINE_IGNORE_DROPOUTS + }; assert!(tags.len() == points.len()); + + let mut params = FT_Raster_Params { + source: &outline as *const _ as *const c_void, + user: &mut bitmap as *mut _ as *mut c_void, gray_spans: Some(span_func), + + // ftgrays.c: `ft_grays_raster` are typoed as `ft_gray_raster` in some comments. + // ftimage.h: FT_Pos can be typed as `signed int` rather than `long`? // XXX: + clip_box: FT_BBox { xMin: 0, yMin: 0, xMax: 0, yMax: 0 }, // FT_RASTER_FLAG_CLIP + flags: FT_RASTER_FLAG_AA | FT_RASTER_FLAG_DIRECT, + black_spans: None, bit_test: None, bit_set: None, // Unused + target: null_mut(), // &bitmap, unused in direct mode + }; let mut raster = null_mut(); + + unsafe { // use of extern static (ft_grays_raster/ft_standard_raster) is unsafe + let _ = ft_grays_raster.raster_new.unwrap()(null_mut(), &mut raster); + //if res != 0 { panic!("raster_new failed: {}", res); } + //ft_grays_raster.raster_reset.unwrap()(raster, null_mut(), 0); + //ft_grays_raster.raster_set_mode.unwrap()(raster, 0, null_mut()); + + let res = ft_grays_raster.raster_render.unwrap()(raster, &mut params); + if res != 0 { panic!("raster_render failed: {res}"); } + //ft_grays_raster.raster_done.unwrap()(raster); + } + + Ok(()) +}