// pathfinder/path-utils/src/stroke.rs // // Copyright © 2017 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use freetype_sys::{FT_Init_FreeType, FT_Library, FT_Outline, FT_STROKER_LINECAP_BUTT, FT_Stroker}; use freetype_sys::{FT_STROKER_LINEJOIN_ROUND, FT_Stroker_BeginSubPath, FT_Stroker_ConicTo}; use freetype_sys::{FT_Stroker_Done, FT_Stroker_EndSubPath, FT_Stroker_Export}; use freetype_sys::{FT_Stroker_GetCounts, FT_Stroker_LineTo, FT_Stroker_New, FT_Stroker_Set}; use freetype_sys::{FT_UInt, FT_Vector}; use std::i16; use freetype::{self, OutlineStream}; use {PathBuffer, PathSegment}; const EPSILON_POSITION_OFFSET: i64 = 8; thread_local! { pub static FREETYPE_LIBRARY: FT_Library = unsafe { let mut library = 0 as FT_Library; assert!(FT_Init_FreeType(&mut library) == 0); library }; } pub fn stroke(output: &mut PathBuffer, stream: I, stroke_width: f32) where I: Iterator { unsafe { let mut stroker = 0 as FT_Stroker; FREETYPE_LIBRARY.with(|&library| { assert!(FT_Stroker_New(library, &mut stroker) == 0); }); // TODO(pcwalton): Make line caps and line join customizable. let stroke_width = freetype::f32_to_26_6_ft_fixed(stroke_width); FT_Stroker_Set(stroker, stroke_width, FT_STROKER_LINECAP_BUTT, FT_STROKER_LINEJOIN_ROUND, 0); let mut first_position_in_subpath = None; for segment in stream { match segment { PathSegment::MoveTo(position) => { if first_position_in_subpath.is_some() { assert!(FT_Stroker_EndSubPath(stroker) == 0); } let mut position = freetype::f32_to_ft_vector(&position); first_position_in_subpath = Some(position); assert!(FT_Stroker_BeginSubPath(stroker, &mut position, 1) == 0); // FIXME(pcwalton): This is a really bad hack to guard against segfaults in // FreeType when paths are empty (e.g. moveto plus closepath). let mut epsilon_position = FT_Vector { x: position.x + EPSILON_POSITION_OFFSET, y: position.y, }; assert!(FT_Stroker_LineTo(stroker, &mut epsilon_position) == 0); } PathSegment::LineTo(position) => { let mut position = freetype::f32_to_ft_vector(&position); assert!(FT_Stroker_LineTo(stroker, &mut position) == 0); } PathSegment::CurveTo(control_point_position, endpoint_position) => { let mut control_point_position = freetype::f32_to_ft_vector(&control_point_position); let mut endpoint_position = freetype::f32_to_ft_vector(&endpoint_position); assert!(FT_Stroker_ConicTo(stroker, &mut control_point_position, &mut endpoint_position) == 0); } PathSegment::ClosePath => { if let Some(mut first_position_in_subpath) = first_position_in_subpath { assert!(FT_Stroker_LineTo(stroker, &mut first_position_in_subpath) == 0); assert!(FT_Stroker_EndSubPath(stroker) == 0); } first_position_in_subpath = None; } } } if first_position_in_subpath.is_some() { assert!(FT_Stroker_EndSubPath(stroker) == 0) } let (mut anum_points, mut anum_contours) = (0, 0); assert!(FT_Stroker_GetCounts(stroker, &mut anum_points, &mut anum_contours) == 0); assert!(anum_points <= i16::MAX as FT_UInt && anum_contours <= i16::MAX as FT_UInt); let mut outline_points = vec![FT_Vector { x: 0, y: 0 }; anum_points as usize]; let mut outline_tags = vec![0; anum_points as usize]; let mut outline_contours = vec![0; anum_contours as usize]; let mut outline = FT_Outline { n_contours: 0, n_points: 0, points: outline_points.as_mut_ptr(), tags: outline_tags.as_mut_ptr(), contours: outline_contours.as_mut_ptr(), flags: 0, }; FT_Stroker_Export(stroker, &mut outline); FT_Stroker_Done(stroker); output.add_stream(OutlineStream::new(&outline, 1.0)); } }