Skip to content

Commit

Permalink
support to compile ftgrays/ftraster/gpac-evg
Browse files Browse the repository at this point in the history
preparing for new renderer implementation
  • Loading branch information
mhfan committed Oct 19, 2023
1 parent 6628058 commit 588fb6e
Show file tree
Hide file tree
Showing 6 changed files with 267 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"C_Cpp.default.defines": [ "STANDALONE_",
"FALL_THROUGH=((void)0)", "FT_BEGIN_HEADER" ],
"C_Cpp.default.includePath": [ "src/evg", "src/raster" ]
}
18 changes: 18 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]

70 changes: 66 additions & 4 deletions build.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,78 @@

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<dyn std::error::Error>> {
//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<String> {
Some(format!("````c,ignore\n{comment}\n````")) // FIXME:
}
}

let module = "ftgrays";
cc::Build::new().flag("-std=c17").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/ftimage.h")
.clang_arg("-DSTANDALONE_").header("src/raster/ftgrays.h")
.clang_arg("-DFT_BEGIN_HEADER=").clang_arg("-DFT_END_HEADER=")
.clang_arg("-DFT_STATIC_BYTE_CAST(type,var)=(type)(unsigned char)(var)")
.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/ft{misc.h,raster.c}} src/raster/
* ln -s /path/to/freetype2/src/{raster/ftmisc.h,base/ft{stroke,trigon}.c} src/raster/stroke/
* ln -s /path/to/freetype2/include/freetype/ft{stroke,trigon,image}.h src/raster/stroke/
* ln -s /path/to/gpac/src/evg/{ftgrays.c,rast_soft.h,stencil.c,surface.c,\
raster_{argb,rgb,565,yuv}.c},raster3d.c src/evg/
* ln -s /path/to/gpac/src/utils/{path2d{,_stroke},math,alloc,color,error}.c src/evg/
* ln -s /path/gpac/include/gpac/{evg,setup,constants,maths,color,path2d,\
tools,thread}.h src/evg/gpac/
* 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(())
}
11 changes: 11 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
7 changes: 7 additions & 0 deletions src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<skia::Pixmap, &str>; }

impl Render for TVGImage { // TODO: to render with femtovg?
Expand Down
160 changes: 160 additions & 0 deletions tests/raster.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@

use core::{ptr::null_mut, ffi::c_void};

#[cfg(feature = "cc")]
#[test] fn raster_evg() -> Result<(), Box<dyn std::error::Error>> {
use intvg::gpac_evg::*;

#[inline] fn int2fix(v: i32) -> Fixed { v << 16 } // TODO:
// /*#[macro_export] */macro_rules! dbg_ne { ($($v:expr),*) => () }
macro_rules! dbg_ne { ($v:expr,$g:expr) => { if $v != $g { dbg!($v); } } }

let (width, height) = (1024_i32, 512_i32);
//let mut bbox = GF_IRect { x: 0, y: 0, width, height };
let mut pixels = vec![0u8; (width * height * 4) as usize];

unsafe {
let surf = gf_evg_surface_new(Bool::GF_FALSE);
let _err = gf_evg_surface_attach_to_buffer(surf, pixels.as_mut_ptr(),
width as u32, height as u32, 4, width << 2, GF_PixelFormat::GF_PIXEL_RGBA);
dbg_ne!(_err, GF_Err::GF_OK);
//let _err = gf_evg_surface_clear(surf, &mut bbox, 0xFF000000);
//dbg_ne!(_err, GF_Err::GF_OK);

let path = gf_path_new();

//gf_path_add_move_to(path, x, y);
//gf_path_add_line_to(path, x, y);
//gf_path_add_cubic_to(path, c1_x, c1_y, c2_x, c2_y, x, y);
//gf_path_add_svg_arc_to(path, end_x, end_y, r_x, r_y, x_rotation, large_arc, sweep);
//gf_path_add_quadratic_to(path, c_x, c_y, x, y);
//gf_path_add_bezier(path, pts, nb_pts);

let _err = gf_path_close(path); dbg_ne!(_err, GF_Err::GF_OK);

let (rw, rh) = (int2fix(width >> 1), int2fix(height >> 1));
//gf_path_add_ellipse(path, cx, cy, a_axis, b_axis);
//gf_path_add_arc(path, radius, start_angle, end_angle, close_type);
gf_path_add_rect_center(path, rw, rh, rw, rh);

let stencil = gf_evg_stencil_new(GF_StencilType::GF_STENCIL_SOLID);

let _err = gf_evg_stencil_set_brush_color(stencil, 0xFF0000FF);
//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);
dbg_ne!(_err, GF_Err::GF_OK);

//let _err = gf_evg_stencil_set_gradient_interpolation(stencil, pos, col, count);
//let _err = gf_evg_stencil_set_gradient_mode(stencil,
// GF_GradientMode::GF_GRADIENT_MODE_PAD);
//let _err = gf_evg_stencil_set_alpha(stencil, alpha);

let _err = gf_evg_surface_set_path(surf, path);
dbg_ne!(_err, GF_Err::GF_OK);
let _err = gf_evg_surface_fill(surf, stencil);
dbg_ne!(_err, GF_Err::GF_OK);

let pen = GF_PenSettings {
width: int2fix(10), cap: 1, join: 1, align: 0, dash: 0,
// XXX: GF_LINE_(CAP/JOIN)_ROUND, GF_PATH_LINE_CENTER, GF_DASH_STYLE_PLAIN
miterLimit: 0, dash_offset: 0, dash_set: null_mut(), path_length: 0,
};
let gpol = gf_path_get_outline(path, pen); // XXX: why not closed?

let _err = gf_evg_stencil_set_brush_color(stencil, 0xAA00FF00);
dbg_ne!(_err, GF_Err::GF_OK);

let _err = gf_evg_surface_set_path(surf, gpol);
dbg_ne!(_err, GF_Err::GF_OK);
let _err = gf_evg_surface_fill(surf, stencil);
dbg_ne!(_err, GF_Err::GF_OK);

gf_path_del(gpol);
gf_path_del(path);
gf_evg_surface_delete(surf);
gf_evg_stencil_delete(stencil);
}

let bufw = std::io::BufWriter::new( //env!("OUT_DIR")
std::fs::File::create(std::path::Path::new(concat!("target", "/image.png")))?);
let mut encoder = png::Encoder::new(bufw,
width as u32, height as u32);
encoder.set_color(png::ColorType::Rgba);
encoder.set_depth(png::BitDepth::Eight);
encoder.set_source_gamma(png::ScaledFloat::new(1.0 / 2.2));
// png::ScaledFloat::from_scaled(45455) // 1.0 / 2.2 scaled by 100000
//let source_chromaticities = png::SourceChromaticities::new( // unscaled instantiation
// (0.31270, 0.32900), (0.64000, 0.33000), (0.30000, 0.60000), (0.15000, 0.06000));
//encoder.set_source_chromaticities(source_chromaticities);
encoder.write_header()?.write_image_data(&pixels)?;

Ok(())
}

#[cfg(feature = "cc")]
#[test] fn raster_ftgrays() -> Result<(), Box<dyn std::error::Error>> {
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(())
}

0 comments on commit 588fb6e

Please sign in to comment.