<commit_before>use rustc_mir::interpret::InterpErrorInfo;
use std::cell::RefCell;

use crate::*;

/// Miri specific diagnostics
pub enum NonHaltingDiagnostic {
    PoppedTrackedPointerTag(Item),
    CreatedAlloc(AllocId),
}

/// Emit a custom diagnostic without going through the miri-engine machinery
pub fn report_diagnostic<'tcx, 'mir>(
    ecx: &InterpCx<'mir, 'tcx, Evaluator<'tcx>>,
    mut e: InterpErrorInfo<'tcx>,
) -> Option<i64> {
    // Special treatment for some error kinds
    let msg = match e.kind {
        InterpError::MachineStop(ref info) => {
            let info = info.downcast_ref::<TerminationInfo>().expect("invalid MachineStop payload");
            match info {
                TerminationInfo::Exit(code) => return Some(*code),
                TerminationInfo::Abort(None) => format!("the evaluated program aborted execution"),
                TerminationInfo::Abort(Some(msg)) => format!("the evaluated program aborted execution: {}", msg),
            }
        }
        err_unsup!(NoMirFor(..)) => format!(
            "{}. Did you set `MIRI_SYSROOT` to a Miri-enabled sysroot? You can prepare one with `cargo miri setup`.",
            e
        ),
        InterpError::InvalidProgram(_) => bug!("This error should be impossible in Miri: {}", e),
        _ => e.to_string(),
    };
    e.print_backtrace();
    report_msg(ecx, msg, true)
}

/// Report an error or note (depending on the `error` argument) at the current frame's current statement.
/// Also emits a full stacktrace of the interpreter stack.
pub fn report_msg<'tcx, 'mir>(
    ecx: &InterpCx<'mir, 'tcx, Evaluator<'tcx>>,
    msg: String,
    error: bool,
) -> Option<i64> {
    if let Some(frame) = ecx.stack().last() {
        let span = frame.current_source_info().unwrap().span;

        let mut err = if error {
            let msg = format!("Miri evaluation error: {}", msg);
            ecx.tcx.sess.struct_span_err(span, msg.as_str())
        } else {
            ecx.tcx.sess.diagnostic().span_note_diag(span, msg.as_str())
        };
        let frames = ecx.generate_stacktrace(None);
        err.span_label(span, msg);
        // We iterate with indices because we need to look at the next frame (the caller).
        for idx in 0..frames.len() {
            let frame_info = &frames[idx];
            let call_site_is_local = frames
                .get(idx + 1)
                .map_or(false, |caller_info| caller_info.instance.def_id().is_local());
            if call_site_is_local {
                err.span_note(frame_info.call_site, &frame_info.to_string());
            } else {
                err.note(&frame_info.to_string());
            }
        }
        err.emit();
    } else {
        ecx.tcx.sess.err(&msg);
    }

    for (i, frame) in ecx.stack().iter().enumerate() {
        trace!("-------------------");
        trace!("Frame {}", i);
        trace!("    return: {:?}", frame.return_place.map(|p| *p));
        for (i, local) in frame.locals.iter().enumerate() {
            trace!("    local {}: {:?}", i, local.value);
        }
    }
    // Let the reported error determine the return code.
    return None;
}

thread_local! {
    static DIAGNOSTICS: RefCell<Vec<NonHaltingDiagnostic>> = RefCell::new(Vec::new());
}

/// Schedule a diagnostic for emitting. This function works even if you have no `InterpCx` available.
/// The diagnostic will be emitted after the current interpreter step is finished.
pub fn register_diagnostic(e: NonHaltingDiagnostic) {
    DIAGNOSTICS.with(|diagnostics| diagnostics.borrow_mut().push(e));
}

impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
    /// Emit all diagnostics that were registed with `register_diagnostics`
    fn process_diagnostics(&self) {
        let this = self.eval_context_ref();
        DIAGNOSTICS.with(|diagnostics| {
            for e in diagnostics.borrow_mut().drain(..) {
                use NonHaltingDiagnostic::*;
                let msg = match e {
                    PoppedTrackedPointerTag(item) =>
                        format!("popped tracked tag for item {:?}", item),
                    CreatedAlloc(AllocId(id)) =>
                        format!("created allocation with id {}", id),
                };
                report_msg(this, msg, false);
            }
        });
    }
}
<commit_msg>give some context in error messages<commit_after>use std::cell::RefCell;

use rustc_span::DUMMY_SP;

use crate::*;

/// Miri specific diagnostics
pub enum NonHaltingDiagnostic {
    PoppedTrackedPointerTag(Item),
    CreatedAlloc(AllocId),
}

/// Emit a custom diagnostic without going through the miri-engine machinery
pub fn report_diagnostic<'tcx, 'mir>(
    ecx: &InterpCx<'mir, 'tcx, Evaluator<'tcx>>,
    mut e: InterpErrorInfo<'tcx>,
) -> Option<i64> {
    use InterpError::*;
    let title = match e.kind {
        Unsupported(_) => "unsupported operation",
        UndefinedBehavior(_) => "Undefined Behavior",
        InvalidProgram(_) => bug!("This error should be impossible in Miri: {}", e),
        ResourceExhaustion(_) => "resource exhaustion",
        MachineStop(_) => "program stopped",
    };
    let msg = match e.kind {
        MachineStop(ref info) => {
            let info = info.downcast_ref::<TerminationInfo>().expect("invalid MachineStop payload");
            match info {
                TerminationInfo::Exit(code) => return Some(*code),
                TerminationInfo::Abort(None) => format!("the evaluated program aborted execution"),
                TerminationInfo::Abort(Some(msg)) => format!("the evaluated program aborted execution: {}", msg),
            }
        }
        _ => e.to_string(),
    };
    let help = match e.kind {
        Unsupported(UnsupportedOpInfo::NoMirFor(..)) =>
            Some("set `MIRI_SYSROOT` to a Miri sysroot, which you can prepare with `cargo miri setup`"),
        Unsupported(_) =>
            Some("this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support"),
        UndefinedBehavior(UndefinedBehaviorInfo::UbExperimental(_)) =>
            Some("this indicates a potential bug in the program: it violated *experimental* rules, and caused Undefined Behavior"),
        UndefinedBehavior(_) =>
            Some("this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior"),
        _ => None,
    };
    e.print_backtrace();
    report_msg(ecx, &format!("{}: {}", title, msg), msg, help, true)
}

/// Report an error or note (depending on the `error` argument) at the current frame's current statement.
/// Also emits a full stacktrace of the interpreter stack.
pub fn report_msg<'tcx, 'mir>(
    ecx: &InterpCx<'mir, 'tcx, Evaluator<'tcx>>,
    title: &str,
    span_msg: String,
    help: Option<&str>,
    error: bool,
) -> Option<i64> {
    let span = if let Some(frame) = ecx.stack().last() {
        frame.current_source_info().unwrap().span
    } else {
        DUMMY_SP
    };
    let mut err = if error {
        ecx.tcx.sess.struct_span_err(span, title)
    } else {
        ecx.tcx.sess.diagnostic().span_note_diag(span, title)
    };
    err.span_label(span, span_msg);
    if let Some(help) = help {
        err.help(help);
    }
    // Add backtrace
    let frames = ecx.generate_stacktrace(None);
    // We iterate with indices because we need to look at the next frame (the caller).
    for idx in 0..frames.len() {
        let frame_info = &frames[idx];
        let call_site_is_local = frames
            .get(idx + 1)
            .map_or(false, |caller_info| caller_info.instance.def_id().is_local());
        if call_site_is_local {
            err.span_note(frame_info.call_site, &frame_info.to_string());
        } else {
            err.note(&frame_info.to_string());
        }
    }

    err.emit();

    for (i, frame) in ecx.stack().iter().enumerate() {
        trace!("-------------------");
        trace!("Frame {}", i);
        trace!("    return: {:?}", frame.return_place.map(|p| *p));
        for (i, local) in frame.locals.iter().enumerate() {
            trace!("    local {}: {:?}", i, local.value);
        }
    }
    // Let the reported error determine the return code.
    return None;
}

thread_local! {
    static DIAGNOSTICS: RefCell<Vec<NonHaltingDiagnostic>> = RefCell::new(Vec::new());
}

/// Schedule a diagnostic for emitting. This function works even if you have no `InterpCx` available.
/// The diagnostic will be emitted after the current interpreter step is finished.
pub fn register_diagnostic(e: NonHaltingDiagnostic) {
    DIAGNOSTICS.with(|diagnostics| diagnostics.borrow_mut().push(e));
}

impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
    /// Emit all diagnostics that were registed with `register_diagnostics`
    fn process_diagnostics(&self) {
        let this = self.eval_context_ref();
        DIAGNOSTICS.with(|diagnostics| {
            for e in diagnostics.borrow_mut().drain(..) {
                use NonHaltingDiagnostic::*;
                let msg = match e {
                    PoppedTrackedPointerTag(item) =>
                        format!("popped tracked tag for item {:?}", item),
                    CreatedAlloc(AllocId(id)) =>
                        format!("created allocation with id {}", id),
                };
                report_msg(this, "tracking was triggered", msg, None, false);
            }
        });
    }
}
<|endoftext|>


<commit_before>#![deny(warnings)]

extern crate orbclient;
extern crate orbtk;

use orbclient::WindowFlag;
use orbtk::{Action, Button, Menu, Point, Rect, Separator, TextBox, Window};
use orbtk::traits::{Click, Place, Resize, Text};

use std::{cmp, env};
use std::fs::File;
use std::io::{Read, Write};

fn main(){
    let path_option = env::args().nth(1);

    let title = if let Some(ref path) = path_option {
        format!("{} - Editor", path)
    } else {
        format!("Editor")
    };

    let (display_width, display_height) = orbclient::get_display_size().expect("viewer: failed to get display size");
    let (width, height) = (cmp::min(1024, display_width * 4/5), cmp::min(768, display_height * 4/5));

    let mut window = Window::new_flags(Rect::new(-1, -1, width, height), &title, &[WindowFlag::Resizable]);

    let text_box = TextBox::new();
    text_box.position(0, 16)
        .size(width, height - 16);
    window.add(&text_box);

    if let Some(ref path) = path_option {
        match File::open(path) {
            Ok(mut file) => {
                let mut text = String::new();
                match file.read_to_string(&mut text) {
                    Ok(_) => text_box.text.set(text),
                    Err(err) => println!("Failed to read {}: {}", path, err)
                }
            },
            Err(err) => println!("Failed to open {}: {}", path, err)
        }
    }

    let menu = Menu::new("File");
    menu.position(0, 0).size(32, 16);

    let open_action = Action::new("Open");
    let open_text_box = text_box.clone();
    open_action.on_click(move |_action: &Action, _point: Point| {
        println!("Open");
        let mut window = Window::new(Rect::new(-1, -1, 320, 8 + 28 + 8 + 28 + 8), "Open");

        let path_box = TextBox::new();
        path_box.position(8, 8)
            .size(320 - 16, 28)
            .text_offset(6, 6)
            .grab_focus(true);
        window.add(&path_box);

        {
            let window_cancel = &mut window as *mut Window;
            let button = Button::new();
            button.position(8, 8 + 28 + 8)
                .size((320 - 16)/2 - 4, 28)
                .text_offset(6, 6)
                .text("Cancel")
                .on_click(move |_button: &Button, _point: Point| {
                    unsafe { (&mut *window_cancel).close(); }
                });
            window.add(&button);
        }

        {
            let open_text_box = open_text_box.clone();
            let window_save_as = &mut window as *mut Window;
            let button = Button::new();
            button.position(320/2 + 4, 8 + 28 + 8)
                .size((320 - 16)/2 - 4, 28)
                .text_offset(6, 6)
                .text("Open")
                .on_click(move |_button: &Button, _point: Point| {
                    match File::open(path_box.text.get()) {
                        Ok(mut f) => {
                            let mut contents = String::new();
                            f.read_to_string(&mut contents).expect("Cannot read file");
                            open_text_box.text.set(contents);
                        },
                        Err(e) => {
                            println!("File does not exist: {}", e);
                        }
                    }
                    unsafe { (&mut *window_save_as).close(); }
                });
            window.add(&button);
        }

        window.exec();

    });
    menu.add(&open_action);

    menu.add(&Separator::new());

    let save_action = Action::new("Save");
    let save_path_option = path_option.clone();
    let save_text_box = text_box.clone();
    save_action.on_click(move |_action: &Action, _point: Point| {
        println!("Save");
        if let Some(ref path) = save_path_option {
            println!("Create {}", path);
            match File::create(path) {
                Ok(mut file) => {
                    let text = save_text_box.text.borrow();
                    match file.write(&text.as_bytes()) {
                        Ok(_) => match file.set_len(text.len() as u64) {
                            Ok(_) => println!("Successfully saved {}", path),
                            Err(err) => println!("Failed to truncate {}: {}", path, err)
                        },
                        Err(err) => println!("Failed to write {}: {}", path, err)
                    }
                },
                Err(err) => println!("Failed to open {}: {}", path, err)
            }
        } else {
            println!("Need to create file!");
        }
    });
    menu.add(&save_action);

    let save_as_action = Action::new("Save As");
    let save_as_path_option = path_option.clone();
    save_as_action.on_click(move |_action: &Action, _point: Point| {
        println!("Save As");
        let mut window = Window::new(Rect::new(-1, -1, 320, 8 + 28 + 8 + 28 + 8), "Save As");

        let text_box = TextBox::new();
        text_box.position(8, 8)
            .size(320 - 16, 28)
            .text_offset(6, 6)
            .grab_focus(true);
        window.add(&text_box);

        if let Some(ref path) = save_as_path_option {
            text_box.text.set(path.clone());
        }

        {
            let window_cancel = &mut window as *mut Window;
            let button = Button::new();
            button.position(8, 8 + 28 + 8)
                .size((320 - 16)/2 - 4, 28)
                .text_offset(6, 6)
                .text("Cancel")
                .on_click(move |_button: &Button, _point: Point| {
                    unsafe { (&mut *window_cancel).close(); }
                });
            window.add(&button);
        }

        {
            let window_save_as = &mut window as *mut Window;
            let button = Button::new();
            button.position(320/2 + 4, 8 + 28 + 8)
                .size((320 - 16)/2 - 4, 28)
                .text_offset(6, 6)
                .text("Save As")
                .on_click(move |_button: &Button, _point: Point| {
                    println!("Save {}", text_box.text.get());
                    unsafe { (&mut *window_save_as).close(); }
                });
            window.add(&button);
        }

        window.exec();
    });
    menu.add(&save_as_action);

    menu.add(&Separator::new());

    let close_action = Action::new("Close");
    let window_close = &mut window as *mut Window;
    close_action.on_click(move |_action: &Action, _point: Point| {
        println!("Close");
        unsafe { (&mut *window_close).close(); }
    });
    menu.add(&close_action);

    window.add(&menu);

    window.on_resize(move |_, width, height| {
        text_box.size(width, height - 16);
    });

    window.exec();
}
<commit_msg>Significant improvements to editor<commit_after>//#![deny(warnings)]

extern crate orbclient;
extern crate orbtk;

use orbclient::WindowFlag;
use orbtk::{Action, Button, Menu, Point, Rect, Separator, TextBox, Window};
use orbtk::traits::{Click, Place, Resize, Text};

use std::{cmp, env};
use std::cell::RefCell;
use std::fs::File;
use std::io::{Read, Write};
use std::ops::DerefMut;
use std::sync::Arc;

pub struct Editor {
    path_option: Option<String>,
    text_box: Arc<TextBox>,
    window: *mut Window,
}

impl Editor {
    pub fn new(path_option: Option<String>, width: u32, height: u32) -> Box<Window> {
        // DESIGN {
        let mut window =  Box::new(Window::new_flags(Rect::new(-1, -1, width, height), "Editor", &[WindowFlag::Resizable]));

        let text_box = TextBox::new();
        text_box.position(0, 16)
            .size(width, height - 16);
        window.add(&text_box);

        let menu = Menu::new("File");
        menu.position(0, 0).size(32, 16);

        let open_action = Action::new("Open");
        menu.add(&open_action);

        let reload_action = Action::new("Reload");
        menu.add(&reload_action);

        menu.add(&Separator::new());

        let save_action = Action::new("Save");
        menu.add(&save_action);

        let save_as_action = Action::new("Save As");
        menu.add(&save_as_action);

        menu.add(&Separator::new());

        let close_action = Action::new("Close");
        menu.add(&close_action);

        window.add(&menu);
        // } DESIGN

        // CODE {
        let editor_cell = Arc::new(RefCell::new(Editor {
            path_option: path_option,
            text_box: text_box.clone(),
            window: window.deref_mut() as *mut Window,
        }));

        {
            let mut editor = editor_cell.borrow_mut();
            if editor.path_option.is_some() {
                editor.load();
            }
        }

        {
            let editor_cell = editor_cell.clone();
            open_action.on_click(move |_action: &Action, _point: Point| {
                println!("Open");

                let mut window = {
                    let editor_dialog = editor_cell.clone();
                    editor_cell.borrow_mut().path_dialog("Open", move |path| {
                        println!("Open {}", path);
                        editor_dialog.borrow_mut().open(&path);
                    })
                };

                window.exec();
            });
        }

        {
            let editor_cell = editor_cell.clone();
            reload_action.on_click(move |_action: &Action, _point: Point| {
                println!("Reload");
                editor_cell.borrow_mut().load();
            });
        }

        {
            let editor_cell = editor_cell.clone();
            save_action.on_click(move |_action: &Action, _point: Point| {
                println!("Save");
                editor_cell.borrow_mut().save();
            });
        }

        {
            let editor_cell = editor_cell.clone();
            save_as_action.on_click(move |_action: &Action, _point: Point| {
                println!("Save As");

                let mut window = {
                    let editor_dialog = editor_cell.clone();
                    editor_cell.borrow_mut().path_dialog("Save As", move |path| {
                        println!("Save As {}", path);
                        editor_dialog.borrow_mut().save_as(&path);
                    })
                };

                window.exec();
            });
        }

        {
            let editor_cell = editor_cell.clone();
            close_action.on_click(move |_action: &Action, _point: Point| {
                println!("Close");
                editor_cell.borrow_mut().close();
            });
        }

        {
            let editor_cell = editor_cell.clone();
            window.on_resize(move |_, width, height| {
                editor_cell.borrow().text_box.size(width, height - 16);
            });
        }
        // } CODE

        window
    }

    fn path_dialog<F: Fn(&str) + 'static>(&mut self, title: &str, func: F) -> Box<Window> {
        let (p_x, p_y, p_w, p_h) = {
            let window = unsafe { &mut *self.window };
            (window.x(), window.y(), window.width(), window.height())
        };

        let w = 320;
        let h = 8 + 28 + 8 + 28 + 8;
        let x = p_x + (p_w as i32 - w as i32)/2;
        let y = p_y + (p_h as i32 - h as i32)/2;
        let mut window = Box::new(Window::new(Rect::new(x, y, w, h), title));

        let text_box = TextBox::new();
        text_box.position(8, 8)
            .size(w - 16, 28)
            .text_offset(6, 6)
            .grab_focus(true);
        window.add(&text_box);

        if let Some(ref path) = self.path_option {
            text_box.text.set(path.clone());
        }

        {
            let window_cancel = window.deref_mut() as *mut Window;
            let button = Button::new();
            button.position(8, 8 + 28 + 8)
                .size((w - 16)/2 - 4, 28)
                .text_offset(6, 6)
                .text("Cancel")
                .on_click(move |_button: &Button, _point: Point| {
                    unsafe { (&mut *window_cancel).close(); }
                });
            window.add(&button);
        }

        {
            let window_save_as = window.deref_mut() as *mut Window;
            let button = Button::new();
            button.position((w as i32)/2 + 4, 8 + 28 + 8)
                .size((w - 16)/2 - 4, 28)
                .text_offset(6, 6)
                .text(title)
                .on_click(move |_button: &Button, _point: Point| {
                    let path = text_box.text.get();
                    func(&path);
                    unsafe { (&mut *window_save_as).close(); }
                });
            window.add(&button);
        }

        window
    }

    fn load(&mut self) {
        if let Some(ref path) = self.path_option {
            println!("Load {}", path);
            match File::open(path) {
                Ok(mut f) => {
                    let mut contents = String::new();
                    match f.read_to_string(&mut contents) {
                        Ok(_) => {
                            self.text_box.text.set(contents);
                        },
                        Err(e) => {
                            println!("Failed to read {}: {}", path, e);
                        }
                    }
                },
                Err(e) => {
                    println!("Failed to open {}: {}", path, e);
                }
            }
        } else {
            println!("Path not set");
        }
    }

    fn save(&mut self) {
        if let Some(ref path) = self.path_option {
            println!("Save {}", path);
            match File::create(path) {
                Ok(mut file) => {
                    let text = self.text_box.text.borrow();
                    match file.write(&text.as_bytes()) {
                        Ok(_) => match file.set_len(text.len() as u64) {
                            Ok(_) => println!("Successfully saved {}", path),
                            Err(err) => println!("Failed to truncate {}: {}", path, err)
                        },
                        Err(err) => println!("Failed to write {}: {}", path, err)
                    }
                },
                Err(err) => println!("Failed to open {}: {}", path, err)
            }
        } else {
            println!("Path not set");
        }
    }

    fn set_path(&mut self, path: &str) {
        self.path_option = Some(path.to_string());
        let window = unsafe { &mut *self.window };
        window.set_title(&format!("{} - Editor", path));
    }

    fn open(&mut self, path: &str) {
        self.set_path(path);
        self.load();
    }

    fn save_as(&mut self, path: &str) {
        self.set_path(path);
        self.save();
    }

    fn close(&mut self) {
        let window = unsafe { &mut *self.window };
        window.close();
    }
}

fn main(){
    let path_option = env::args().nth(1);

    let (display_width, display_height) = orbclient::get_display_size().expect("viewer: failed to get display size");
    let (width, height) = (cmp::min(1024, display_width * 4/5), cmp::min(768, display_height * 4/5));

    Editor::new(path_option, width, height).exec();
}
<|endoftext|>


<commit_before>/// Implementation guts for `min_set`, `min_set_by`, and `min_set_by_key`.
pub fn min_set_impl<I, K, F, L>(mut it: I,
                                mut key_for: F,
                                mut lt: L) -> Vec<I::Item>
    where I: Iterator,
          F: FnMut(&I::Item) -> K,
          L: FnMut(&I::Item, &I::Item, &K, &K) -> bool,
{
    let (mut result, mut current_key) = match it.next() {
        None => return Vec::new(),
        Some(element) => {
            let key = key_for(&element);
            (vec![element], key)
        }
    };

    for element in it {
        let key = key_for(&element);
        if lt(&element, &result[0], &key, &current_key) {
            result.clear();
            result.push(element);
            current_key = key;
        } else if !lt(&result[0], &element, &current_key, &key) {
            result.push(element);
        }
    }

    result
}

/// Implementation guts for `ax_set`, `max_set_by`, and `max_set_by_key`.
pub fn max_set_impl<I, K, F, L>(it: I,
                                key_for: F,
                                mut lt: L) -> Vec<I::Item>
    where I: Iterator,
          F: FnMut(&I::Item) -> K,
          L: FnMut(&I::Item, &I::Item, &K, &K) -> bool,
{
    min_set_impl(it, key_for, |it1, it2, key1, key2| lt(it2, it1, key2, key1))
}

<commit_msg>Add {min,max}_set(_by{_key)?)? functions (4) use internal iteration<commit_after>/// Implementation guts for `min_set`, `min_set_by`, and `min_set_by_key`.
pub fn min_set_impl<I, K, F, L>(mut it: I,
                                mut key_for: F,
                                mut lt: L) -> Vec<I::Item>
    where I: Iterator,
          F: FnMut(&I::Item) -> K,
          L: FnMut(&I::Item, &I::Item, &K, &K) -> bool,
{
    let (mut result, mut current_key) = match it.next() {
        None => return Vec::new(),
        Some(element) => {
            let key = key_for(&element);
            (vec![element], key)
        }
    };

    it.for_each(|element| {
        let key = key_for(&element);
        if lt(&element, &result[0], &key, &current_key) {
            result.clear();
            result.push(element);
            current_key = key;
        } else if !lt(&result[0], &element, &current_key, &key) {
            result.push(element);
        }
    });

    result
}

/// Implementation guts for `ax_set`, `max_set_by`, and `max_set_by_key`.
pub fn max_set_impl<I, K, F, L>(it: I,
                                key_for: F,
                                mut lt: L) -> Vec<I::Item>
    where I: Iterator,
          F: FnMut(&I::Item) -> K,
          L: FnMut(&I::Item, &I::Item, &K, &K) -> bool,
{
    min_set_impl(it, key_for, |it1, it2, key1, key2| lt(it2, it1, key2, key1))
}

<|endoftext|>


<commit_before>use cssparser::Parser;

use crate::error::*;
use crate::filter::Filter;
use crate::filters::{
    gaussian_blur::GaussianBlur, FilterResolveError, FilterSpec, Primitive, PrimitiveParams,
    ResolvedPrimitive,
};
use crate::length::*;
use crate::parsers::{NumberOrPercentage, Parse};
use crate::properties::ComputedValues;
use crate::{coord_units::CoordUnits, filters::color_matrix::ColorMatrix};
use crate::{drawing_ctx::DrawingCtx, filters::component_transfer};

/// CSS Filter functions from the Filter Effects Module Level 1
///
/// https://www.w3.org/TR/filter-effects/#filter-functions
#[derive(Debug, Clone, PartialEq)]
pub enum FilterFunction {
    Blur(Blur),
    Grayscale(Grayscale),
    Opacity(Opacity),
    Saturate(Saturate),
    Sepia(Sepia),
}

/// Parameters for the `blur()` filter function
///
/// https://www.w3.org/TR/filter-effects/#funcdef-filter-blur
#[derive(Debug, Clone, PartialEq)]
pub struct Blur {
    std_deviation: Option<Length<Both>>,
}

/// Parameters for the `grayscale()` filter function
///
/// https://www.w3.org/TR/filter-effects/#funcdef-filter-grayscale
#[derive(Debug, Clone, PartialEq)]
pub struct Grayscale {
    proportion: Option<f64>,
}

/// Parameters for the `opacity()` filter function
///
/// https://www.w3.org/TR/filter-effects/#funcdef-filter-opacity
#[derive(Debug, Clone, PartialEq)]
pub struct Opacity {
    proportion: Option<f64>,
}

/// Parameters for the `saturate()` filter function
///
/// https://www.w3.org/TR/filter-effects/#funcdef-filter-saturate
#[derive(Debug, Clone, PartialEq)]
pub struct Saturate {
    proportion: Option<f64>,
}

/// Parameters for the `sepia()` filter function
///
/// https://www.w3.org/TR/filter-effects/#funcdef-filter-sepia
#[derive(Debug, Clone, PartialEq)]
pub struct Sepia {
    proportion: Option<f64>,
}

fn parse_function<'i, F>(
    parser: &mut Parser<'i, '_>,
    name: &str,
    f: F,
) -> Result<FilterFunction, ParseError<'i>>
where
    F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result<FilterFunction, ParseError<'i>>,
{
    parser.expect_function_matching(name)?;
    parser.parse_nested_block(f)
}

// This function doesn't fail, but returns a Result like the other parsers, so tell Clippy
// about that.
#[allow(clippy::unnecessary_wraps)]
fn parse_blur<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
    let length = if let Ok(length) = parser.try_parse(|p| Length::parse(p)) {
        Some(length)
    } else {
        None
    };

    Ok(FilterFunction::Blur(Blur {
        std_deviation: length,
    }))
}

#[allow(clippy::unnecessary_wraps)]
fn parse_grayscale<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
    let proportion = match parser.try_parse(|p| NumberOrPercentage::parse(p)) {
        Ok(NumberOrPercentage { value }) if value < 0.0 => None,
        Ok(NumberOrPercentage { value }) => Some(value.clamp(0.0, 1.0)),
        Err(_) => None,
    };

    Ok(FilterFunction::Grayscale(Grayscale { proportion }))
}

#[allow(clippy::unnecessary_wraps)]
fn parse_opacity<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
    let proportion = match parser.try_parse(|p| NumberOrPercentage::parse(p)) {
        Ok(NumberOrPercentage { value }) if value < 0.0 => None,
        Ok(NumberOrPercentage { value }) => Some(value.clamp(0.0, 1.0)),
        Err(_) => None,
    };

    Ok(FilterFunction::Opacity(Opacity { proportion }))
}

#[allow(clippy::unnecessary_wraps)]
fn parse_saturate<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
    let proportion = match parser.try_parse(|p| NumberOrPercentage::parse(p)) {
        Ok(NumberOrPercentage { value }) if value < 0.0 => None,
        Ok(NumberOrPercentage { value }) => Some(value),
        Err(_) => None,
    };

    Ok(FilterFunction::Saturate(Saturate { proportion }))
}

#[allow(clippy::unnecessary_wraps)]
fn parse_sepia<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
    let proportion = match parser.try_parse(|p| NumberOrPercentage::parse(p)) {
        Ok(NumberOrPercentage { value }) if value < 0.0 => None,
        Ok(NumberOrPercentage { value }) => Some(value.clamp(0.0, 1.0)),
        Err(_) => None,
    };

    Ok(FilterFunction::Sepia(Sepia { proportion }))
}

impl Blur {
    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
        // The 0.0 default is from the spec
        let std_dev = self.std_deviation.map(|l| l.to_user(params)).unwrap_or(0.0);

        let user_space_filter = Filter::default().to_user_space(params);

        let gaussian_blur = ResolvedPrimitive {
            primitive: Primitive::default(),
            params: PrimitiveParams::GaussianBlur(GaussianBlur {
                std_deviation: (std_dev, std_dev),
                ..GaussianBlur::default()
            }),
        }
        .into_user_space(params);

        FilterSpec {
            user_space_filter,
            primitives: vec![gaussian_blur],
        }
    }
}

impl Grayscale {
    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
        // grayscale is implemented as the inverse of a saturate operation,
        // with the input clamped to the range [0, 1] by the parser.
        let p = 1.0 - self.proportion.unwrap_or(1.0);
        let saturate = Saturate {
            proportion: Some(p),
        };

        saturate.to_filter_spec(params)
    }
}

impl Opacity {
    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
        let p = self.proportion.unwrap_or(1.0);
        let user_space_filter = Filter::default().to_user_space(params);

        let opacity = ResolvedPrimitive {
            primitive: Primitive::default(),
            params: PrimitiveParams::ComponentTransfer(component_transfer::ComponentTransfer {
                functions: crate::filters::component_transfer::Functions {
                    a: component_transfer::FeFuncA {
                        function_type: component_transfer::FunctionType::Table,
                        table_values: vec![0.0, p],
                        ..component_transfer::FeFuncA::default()
                    },
                    ..component_transfer::Functions::default()
                },
                ..component_transfer::ComponentTransfer::default()
            }),
        }
        .into_user_space(params);

        FilterSpec {
            user_space_filter,
            primitives: vec![opacity],
        }
    }
}

impl Saturate {
    #[rustfmt::skip]
    fn matrix(&self) -> nalgebra::Matrix5<f64> {
        let p = self.proportion.unwrap_or(1.0);

        nalgebra::Matrix5::new(
            0.213 + 0.787 * p, 0.715 - 0.715 * p, 0.072 - 0.072 * p, 0.0, 0.0,
            0.213 - 0.213 * p, 0.715 + 0.285 * p, 0.072 - 0.072 * p, 0.0, 0.0,
            0.213 - 0.213 * p, 0.715 - 0.715 * p, 0.072 + 0.928 * p, 0.0, 0.0,
            0.0,               0.0,               0.0,               1.0, 0.0,
            0.0,               0.0,               0.0,               0.0, 1.0,
        )
    }

    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
        let user_space_filter = Filter::default().to_user_space(params);

        let saturate = ResolvedPrimitive {
            primitive: Primitive::default(),
            params: PrimitiveParams::ColorMatrix(ColorMatrix {
                matrix: self.matrix(),
                ..ColorMatrix::default()
            }),
        }
        .into_user_space(params);

        FilterSpec {
            user_space_filter,
            primitives: vec![saturate],
        }
    }
}

impl Sepia {
    #[rustfmt::skip]
    fn matrix(&self) -> nalgebra::Matrix5<f64> {
        let p = self.proportion.unwrap_or(1.0);

        nalgebra::Matrix5::new(
            0.393 + 0.607 * (1.0 - p), 0.769 - 0.769 * (1.0 - p), 0.189 - 0.189 * (1.0 - p), 0.0, 0.0,
            0.349 - 0.349 * (1.0 - p), 0.686 + 0.314 * (1.0 - p), 0.168 - 0.168 * (1.0 - p), 0.0, 0.0,
            0.272 - 0.272 * (1.0 - p), 0.534 - 0.534 * (1.0 - p), 0.131 + 0.869 * (1.0 - p), 0.0, 0.0,
            0.0,                       0.0,                       0.0,                       1.0, 0.0,
            0.0,                       0.0,                       0.0,                       0.0, 1.0,
        )
    }

    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
        let user_space_filter = Filter::default().to_user_space(params);

        let sepia = ResolvedPrimitive {
            primitive: Primitive::default(),
            params: PrimitiveParams::ColorMatrix(ColorMatrix {
                matrix: self.matrix(),
                ..ColorMatrix::default()
            }),
        }
        .into_user_space(params);

        FilterSpec {
            user_space_filter,
            primitives: vec![sepia],
        }
    }
}

impl Parse for FilterFunction {
    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, crate::error::ParseError<'i>> {
        let loc = parser.current_source_location();
        let fns: Vec<(&str, &dyn Fn(&mut Parser<'i, '_>) -> _)> = vec![
            ("blur", &parse_blur),
            ("grayscale", &parse_grayscale),
            ("opacity", &parse_opacity),
            ("saturate", &parse_saturate),
            ("sepia", &parse_sepia),
        ];

        for (filter_name, parse_fn) in fns {
            if let Ok(func) = parser.try_parse(|p| parse_function(p, filter_name, parse_fn)) {
                return Ok(func);
            }
        }

        return Err(loc.new_custom_error(ValueErrorKind::parse_error("expected filter function")));
    }
}

impl FilterFunction {
    // If this function starts actually returning an Err, remove this Clippy exception:
    #[allow(clippy::unnecessary_wraps)]
    pub fn to_filter_spec(
        &self,
        values: &ComputedValues,
        draw_ctx: &DrawingCtx,
    ) -> Result<FilterSpec, FilterResolveError> {
        // userSpaceonUse is the default for primitive_units
        let view_params = draw_ctx.push_coord_units(CoordUnits::UserSpaceOnUse);
        let params = NormalizeParams::new(values, &view_params);

        match self {
            FilterFunction::Blur(v) => Ok(v.to_filter_spec(&params)),
            FilterFunction::Grayscale(v) => Ok(v.to_filter_spec(&params)),
            FilterFunction::Opacity(v) => Ok(v.to_filter_spec(&params)),
            FilterFunction::Saturate(v) => Ok(v.to_filter_spec(&params)),
            FilterFunction::Sepia(v) => Ok(v.to_filter_spec(&params)),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parses_blur() {
        assert_eq!(
            FilterFunction::parse_str("blur()").unwrap(),
            FilterFunction::Blur(Blur {
                std_deviation: None
            })
        );

        assert_eq!(
            FilterFunction::parse_str("blur(5px)").unwrap(),
            FilterFunction::Blur(Blur {
                std_deviation: Some(Length::new(5.0, LengthUnit::Px))
            })
        );
    }

    #[test]
    fn parses_grayscale() {
        assert_eq!(
            FilterFunction::parse_str("grayscale()").unwrap(),
            FilterFunction::Grayscale(Grayscale { proportion: None })
        );

        assert_eq!(
            FilterFunction::parse_str("grayscale(50%)").unwrap(),
            FilterFunction::Grayscale(Grayscale {
                proportion: Some(0.50_f32.into()),
            })
        );
    }

    #[test]
    fn parses_opacity() {
        assert_eq!(
            FilterFunction::parse_str("opacity()").unwrap(),
            FilterFunction::Opacity(Opacity { proportion: None })
        );

        assert_eq!(
            FilterFunction::parse_str("opacity(50%)").unwrap(),
            FilterFunction::Opacity(Opacity {
                proportion: Some(0.50_f32.into()),
            })
        );
    }

    #[test]
    fn parses_saturate() {
        assert_eq!(
            FilterFunction::parse_str("saturate()").unwrap(),
            FilterFunction::Saturate(Saturate { proportion: None })
        );

        assert_eq!(
            FilterFunction::parse_str("saturate(50%)").unwrap(),
            FilterFunction::Saturate(Saturate {
                proportion: Some(0.50_f32.into()),
            })
        );
    }

    #[test]
    fn parses_sepia() {
        assert_eq!(
            FilterFunction::parse_str("sepia()").unwrap(),
            FilterFunction::Sepia(Sepia { proportion: None })
        );

        assert_eq!(
            FilterFunction::parse_str("sepia(80%)").unwrap(),
            FilterFunction::Sepia(Sepia {
                proportion: Some(0.80_f32.into())
            })
        );

        assert_eq!(
            FilterFunction::parse_str("sepia(0.52)").unwrap(),
            FilterFunction::Sepia(Sepia {
                proportion: Some(0.52_f32.into())
            })
        );

        // values > 1.0 should be clamped to 1.0
        assert_eq!(
            FilterFunction::parse_str("sepia(1.5)").unwrap(),
            FilterFunction::Sepia(Sepia {
                proportion: Some(1.0)
            })
        );

        // negative numbers are invalid.
        assert_eq!(
            FilterFunction::parse_str("sepia(-1)").unwrap(),
            FilterFunction::Sepia(Sepia { proportion: None }),
        );
    }

    #[test]
    fn invalid_blur_yields_error() {
        assert!(FilterFunction::parse_str("blur(foo)").is_err());
        assert!(FilterFunction::parse_str("blur(42 43)").is_err());
    }

    #[test]
    fn invalid_grayscale_yields_error() {
        assert!(FilterFunction::parse_str("grayscale(foo)").is_err());
    }

    #[test]
    fn invalid_opacity_yields_error() {
        assert!(FilterFunction::parse_str("opacity(foo)").is_err());
    }

    #[test]
    fn invalid_saturate_yields_error() {
        assert!(FilterFunction::parse_str("saturate(foo)").is_err());
    }

    #[test]
    fn invalid_sepia_yields_error() {
        assert!(FilterFunction::parse_str("sepia(foo)").is_err());
    }
}
<commit_msg>Replace duplicated num-or-percentage parse functions with helpers<commit_after>use cssparser::Parser;

use crate::error::*;
use crate::filter::Filter;
use crate::filters::{
    gaussian_blur::GaussianBlur, FilterResolveError, FilterSpec, Primitive, PrimitiveParams,
    ResolvedPrimitive,
};
use crate::length::*;
use crate::parsers::{NumberOrPercentage, Parse};
use crate::properties::ComputedValues;
use crate::{coord_units::CoordUnits, filters::color_matrix::ColorMatrix};
use crate::{drawing_ctx::DrawingCtx, filters::component_transfer};

/// CSS Filter functions from the Filter Effects Module Level 1
///
/// https://www.w3.org/TR/filter-effects/#filter-functions
#[derive(Debug, Clone, PartialEq)]
pub enum FilterFunction {
    Blur(Blur),
    Grayscale(Grayscale),
    Opacity(Opacity),
    Saturate(Saturate),
    Sepia(Sepia),
}

/// Parameters for the `blur()` filter function
///
/// https://www.w3.org/TR/filter-effects/#funcdef-filter-blur
#[derive(Debug, Clone, PartialEq)]
pub struct Blur {
    std_deviation: Option<Length<Both>>,
}

/// Parameters for the `grayscale()` filter function
///
/// https://www.w3.org/TR/filter-effects/#funcdef-filter-grayscale
#[derive(Debug, Clone, PartialEq)]
pub struct Grayscale {
    proportion: Option<f64>,
}

/// Parameters for the `opacity()` filter function
///
/// https://www.w3.org/TR/filter-effects/#funcdef-filter-opacity
#[derive(Debug, Clone, PartialEq)]
pub struct Opacity {
    proportion: Option<f64>,
}

/// Parameters for the `saturate()` filter function
///
/// https://www.w3.org/TR/filter-effects/#funcdef-filter-saturate
#[derive(Debug, Clone, PartialEq)]
pub struct Saturate {
    proportion: Option<f64>,
}

/// Parameters for the `sepia()` filter function
///
/// https://www.w3.org/TR/filter-effects/#funcdef-filter-sepia
#[derive(Debug, Clone, PartialEq)]
pub struct Sepia {
    proportion: Option<f64>,
}

fn parse_function<'i, F>(
    parser: &mut Parser<'i, '_>,
    name: &str,
    f: F,
) -> Result<FilterFunction, ParseError<'i>>
where
    F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result<FilterFunction, ParseError<'i>>,
{
    parser.expect_function_matching(name)?;
    parser.parse_nested_block(f)
}

// This function doesn't fail, but returns a Result like the other parsers, so tell Clippy
// about that.
#[allow(clippy::unnecessary_wraps)]
fn parse_blur<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
    let length = if let Ok(length) = parser.try_parse(|p| Length::parse(p)) {
        Some(length)
    } else {
        None
    };

    Ok(FilterFunction::Blur(Blur {
        std_deviation: length,
    }))
}

/// Reads an optional number or percentage from the parser.
/// Negative numbers are not allowed.
fn parse_num_or_percentage<'i>(parser: &mut Parser<'i, '_>) -> Option<f64> {
    match parser.try_parse(|p| NumberOrPercentage::parse(p)) {
        Ok(NumberOrPercentage { value }) if value < 0.0 => None,
        Ok(NumberOrPercentage { value }) => Some(value),
        Err(_) => None,
    }
}

/// Reads an optional number or percentage from the parser, returning a value clamped to [0, 1].
/// Negative numbers are not allowed.
fn parse_num_or_percentage_clamped<'i>(parser: &mut Parser<'i, '_>) -> Option<f64> {
    parse_num_or_percentage(parser).map(|value| value.clamp(0.0, 1.0))
}

#[allow(clippy::unnecessary_wraps)]
fn parse_grayscale<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
    let proportion = parse_num_or_percentage_clamped(parser);

    Ok(FilterFunction::Grayscale(Grayscale { proportion }))
}

#[allow(clippy::unnecessary_wraps)]
fn parse_opacity<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
    let proportion = parse_num_or_percentage_clamped(parser);

    Ok(FilterFunction::Opacity(Opacity { proportion }))
}

#[allow(clippy::unnecessary_wraps)]
fn parse_saturate<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
    let proportion = parse_num_or_percentage(parser);

    Ok(FilterFunction::Saturate(Saturate { proportion }))
}

#[allow(clippy::unnecessary_wraps)]
fn parse_sepia<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
    let proportion = parse_num_or_percentage_clamped(parser);

    Ok(FilterFunction::Sepia(Sepia { proportion }))
}

impl Blur {
    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
        // The 0.0 default is from the spec
        let std_dev = self.std_deviation.map(|l| l.to_user(params)).unwrap_or(0.0);

        let user_space_filter = Filter::default().to_user_space(params);

        let gaussian_blur = ResolvedPrimitive {
            primitive: Primitive::default(),
            params: PrimitiveParams::GaussianBlur(GaussianBlur {
                std_deviation: (std_dev, std_dev),
                ..GaussianBlur::default()
            }),
        }
        .into_user_space(params);

        FilterSpec {
            user_space_filter,
            primitives: vec![gaussian_blur],
        }
    }
}

impl Grayscale {
    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
        // grayscale is implemented as the inverse of a saturate operation,
        // with the input clamped to the range [0, 1] by the parser.
        let p = 1.0 - self.proportion.unwrap_or(1.0);
        let saturate = Saturate {
            proportion: Some(p),
        };

        saturate.to_filter_spec(params)
    }
}

impl Opacity {
    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
        let p = self.proportion.unwrap_or(1.0);
        let user_space_filter = Filter::default().to_user_space(params);

        let opacity = ResolvedPrimitive {
            primitive: Primitive::default(),
            params: PrimitiveParams::ComponentTransfer(component_transfer::ComponentTransfer {
                functions: crate::filters::component_transfer::Functions {
                    a: component_transfer::FeFuncA {
                        function_type: component_transfer::FunctionType::Table,
                        table_values: vec![0.0, p],
                        ..component_transfer::FeFuncA::default()
                    },
                    ..component_transfer::Functions::default()
                },
                ..component_transfer::ComponentTransfer::default()
            }),
        }
        .into_user_space(params);

        FilterSpec {
            user_space_filter,
            primitives: vec![opacity],
        }
    }
}

impl Saturate {
    #[rustfmt::skip]
    fn matrix(&self) -> nalgebra::Matrix5<f64> {
        let p = self.proportion.unwrap_or(1.0);

        nalgebra::Matrix5::new(
            0.213 + 0.787 * p, 0.715 - 0.715 * p, 0.072 - 0.072 * p, 0.0, 0.0,
            0.213 - 0.213 * p, 0.715 + 0.285 * p, 0.072 - 0.072 * p, 0.0, 0.0,
            0.213 - 0.213 * p, 0.715 - 0.715 * p, 0.072 + 0.928 * p, 0.0, 0.0,
            0.0,               0.0,               0.0,               1.0, 0.0,
            0.0,               0.0,               0.0,               0.0, 1.0,
        )
    }

    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
        let user_space_filter = Filter::default().to_user_space(params);

        let saturate = ResolvedPrimitive {
            primitive: Primitive::default(),
            params: PrimitiveParams::ColorMatrix(ColorMatrix {
                matrix: self.matrix(),
                ..ColorMatrix::default()
            }),
        }
        .into_user_space(params);

        FilterSpec {
            user_space_filter,
            primitives: vec![saturate],
        }
    }
}

impl Sepia {
    #[rustfmt::skip]
    fn matrix(&self) -> nalgebra::Matrix5<f64> {
        let p = self.proportion.unwrap_or(1.0);

        nalgebra::Matrix5::new(
            0.393 + 0.607 * (1.0 - p), 0.769 - 0.769 * (1.0 - p), 0.189 - 0.189 * (1.0 - p), 0.0, 0.0,
            0.349 - 0.349 * (1.0 - p), 0.686 + 0.314 * (1.0 - p), 0.168 - 0.168 * (1.0 - p), 0.0, 0.0,
            0.272 - 0.272 * (1.0 - p), 0.534 - 0.534 * (1.0 - p), 0.131 + 0.869 * (1.0 - p), 0.0, 0.0,
            0.0,                       0.0,                       0.0,                       1.0, 0.0,
            0.0,                       0.0,                       0.0,                       0.0, 1.0,
        )
    }

    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
        let user_space_filter = Filter::default().to_user_space(params);

        let sepia = ResolvedPrimitive {
            primitive: Primitive::default(),
            params: PrimitiveParams::ColorMatrix(ColorMatrix {
                matrix: self.matrix(),
                ..ColorMatrix::default()
            }),
        }
        .into_user_space(params);

        FilterSpec {
            user_space_filter,
            primitives: vec![sepia],
        }
    }
}

impl Parse for FilterFunction {
    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, crate::error::ParseError<'i>> {
        let loc = parser.current_source_location();
        let fns: Vec<(&str, &dyn Fn(&mut Parser<'i, '_>) -> _)> = vec![
            ("blur", &parse_blur),
            ("grayscale", &parse_grayscale),
            ("opacity", &parse_opacity),
            ("saturate", &parse_saturate),
            ("sepia", &parse_sepia),
        ];

        for (filter_name, parse_fn) in fns {
            if let Ok(func) = parser.try_parse(|p| parse_function(p, filter_name, parse_fn)) {
                return Ok(func);
            }
        }

        return Err(loc.new_custom_error(ValueErrorKind::parse_error("expected filter function")));
    }
}

impl FilterFunction {
    // If this function starts actually returning an Err, remove this Clippy exception:
    #[allow(clippy::unnecessary_wraps)]
    pub fn to_filter_spec(
        &self,
        values: &ComputedValues,
        draw_ctx: &DrawingCtx,
    ) -> Result<FilterSpec, FilterResolveError> {
        // userSpaceonUse is the default for primitive_units
        let view_params = draw_ctx.push_coord_units(CoordUnits::UserSpaceOnUse);
        let params = NormalizeParams::new(values, &view_params);

        match self {
            FilterFunction::Blur(v) => Ok(v.to_filter_spec(&params)),
            FilterFunction::Grayscale(v) => Ok(v.to_filter_spec(&params)),
            FilterFunction::Opacity(v) => Ok(v.to_filter_spec(&params)),
            FilterFunction::Saturate(v) => Ok(v.to_filter_spec(&params)),
            FilterFunction::Sepia(v) => Ok(v.to_filter_spec(&params)),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parses_blur() {
        assert_eq!(
            FilterFunction::parse_str("blur()").unwrap(),
            FilterFunction::Blur(Blur {
                std_deviation: None
            })
        );

        assert_eq!(
            FilterFunction::parse_str("blur(5px)").unwrap(),
            FilterFunction::Blur(Blur {
                std_deviation: Some(Length::new(5.0, LengthUnit::Px))
            })
        );
    }

    #[test]
    fn parses_grayscale() {
        assert_eq!(
            FilterFunction::parse_str("grayscale()").unwrap(),
            FilterFunction::Grayscale(Grayscale { proportion: None })
        );

        assert_eq!(
            FilterFunction::parse_str("grayscale(50%)").unwrap(),
            FilterFunction::Grayscale(Grayscale {
                proportion: Some(0.50_f32.into()),
            })
        );
    }

    #[test]
    fn parses_opacity() {
        assert_eq!(
            FilterFunction::parse_str("opacity()").unwrap(),
            FilterFunction::Opacity(Opacity { proportion: None })
        );

        assert_eq!(
            FilterFunction::parse_str("opacity(50%)").unwrap(),
            FilterFunction::Opacity(Opacity {
                proportion: Some(0.50_f32.into()),
            })
        );
    }

    #[test]
    fn parses_saturate() {
        assert_eq!(
            FilterFunction::parse_str("saturate()").unwrap(),
            FilterFunction::Saturate(Saturate { proportion: None })
        );

        assert_eq!(
            FilterFunction::parse_str("saturate(50%)").unwrap(),
            FilterFunction::Saturate(Saturate {
                proportion: Some(0.50_f32.into()),
            })
        );
    }

    #[test]
    fn parses_sepia() {
        assert_eq!(
            FilterFunction::parse_str("sepia()").unwrap(),
            FilterFunction::Sepia(Sepia { proportion: None })
        );

        assert_eq!(
            FilterFunction::parse_str("sepia(80%)").unwrap(),
            FilterFunction::Sepia(Sepia {
                proportion: Some(0.80_f32.into())
            })
        );

        assert_eq!(
            FilterFunction::parse_str("sepia(0.52)").unwrap(),
            FilterFunction::Sepia(Sepia {
                proportion: Some(0.52_f32.into())
            })
        );

        // values > 1.0 should be clamped to 1.0
        assert_eq!(
            FilterFunction::parse_str("sepia(1.5)").unwrap(),
            FilterFunction::Sepia(Sepia {
                proportion: Some(1.0)
            })
        );

        // negative numbers are invalid.
        assert_eq!(
            FilterFunction::parse_str("sepia(-1)").unwrap(),
            FilterFunction::Sepia(Sepia { proportion: None }),
        );
    }

    #[test]
    fn invalid_blur_yields_error() {
        assert!(FilterFunction::parse_str("blur(foo)").is_err());
        assert!(FilterFunction::parse_str("blur(42 43)").is_err());
    }

    #[test]
    fn invalid_grayscale_yields_error() {
        assert!(FilterFunction::parse_str("grayscale(foo)").is_err());
    }

    #[test]
    fn invalid_opacity_yields_error() {
        assert!(FilterFunction::parse_str("opacity(foo)").is_err());
    }

    #[test]
    fn invalid_saturate_yields_error() {
        assert!(FilterFunction::parse_str("saturate(foo)").is_err());
    }

    #[test]
    fn invalid_sepia_yields_error() {
        assert!(FilterFunction::parse_str("sepia(foo)").is_err());
    }
}
<|endoftext|>


<commit_before>//! Entry point for the CSS filters infrastructure.

use cssparser::{BasicParseError, Parser};
use markup5ever::{expanded_name, local_name, namespace_url, ns};
use std::time::Instant;

use crate::bbox::BoundingBox;
use crate::coord_units::CoordUnits;
use crate::document::AcquiredNodes;
use crate::drawing_ctx::DrawingCtx;
use crate::element::{Draw, ElementResult, SetAttributes};
use crate::error::{ElementError, ParseError, RenderingError};
use crate::length::*;
use crate::node::{Node, NodeBorrow};
use crate::parsers::{CustomIdent, Parse, ParseValue};
use crate::properties::ComputedValues;
use crate::property_defs::ColorInterpolationFilters;
use crate::surface_utils::shared_surface::{SharedImageSurface, SurfaceType};
use crate::transform::Transform;
use crate::xml::Attributes;

mod bounds;
use self::bounds::BoundsBuilder;

pub mod context;
use self::context::{FilterContext, FilterResult};

mod error;
use self::error::FilterError;

/// Trait to render filter effect primitives.
pub trait FilterRender {
    /// Renders this filter primitive.
    ///
    /// If this filter primitive can't be rendered for whatever reason (for instance, a required
    /// property hasn't been provided), an error is returned.
    fn render(
        &self,
        node: &Node,
        ctx: &FilterContext,
        acquired_nodes: &mut AcquiredNodes<'_>,
        draw_ctx: &mut DrawingCtx,
    ) -> Result<FilterResult, FilterError>;
}

/// A filter primitive interface.
pub trait FilterEffect: SetAttributes + Draw + FilterRender {}

// Filter Effects do not need to draw themselves
impl<T: FilterEffect> Draw for T {}

pub mod blend;
pub mod color_matrix;
pub mod component_transfer;
pub mod composite;
pub mod convolve_matrix;
pub mod displacement_map;
pub mod flood;
pub mod gaussian_blur;
pub mod image;
pub mod lighting;
pub mod merge;
pub mod morphology;
pub mod offset;
pub mod tile;
pub mod turbulence;

/// The base filter primitive node containing common properties.
struct Primitive {
    x: Option<Length<Horizontal>>,
    y: Option<Length<Vertical>>,
    width: Option<ULength<Horizontal>>,
    height: Option<ULength<Vertical>>,
    result: Option<CustomIdent>,
}

/// An enumeration of possible inputs for a filter primitive.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum Input {
    Unspecified,
    SourceGraphic,
    SourceAlpha,
    BackgroundImage,
    BackgroundAlpha,
    FillPaint,
    StrokePaint,
    FilterOutput(CustomIdent),
}

impl Default for Input {
    fn default() -> Self {
        Input::Unspecified
    }
}

impl Parse for Input {
    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
        parser
            .try_parse(|p| {
                Ok(parse_identifiers!(
                    p,
                    "SourceGraphic" => Input::SourceGraphic,
                    "SourceAlpha" => Input::SourceAlpha,
                    "BackgroundImage" => Input::BackgroundImage,
                    "BackgroundAlpha" => Input::BackgroundAlpha,
                    "FillPaint" => Input::FillPaint,
                    "StrokePaint" => Input::StrokePaint,
                )?)
            })
            .or_else(|_: BasicParseError<'_>| {
                let ident = CustomIdent::parse(parser)?;
                Ok(Input::FilterOutput(ident))
            })
    }
}

impl Primitive {
    /// Constructs a new `Primitive` with empty properties.
    #[inline]
    fn new() -> Primitive {
        Primitive {
            x: None,
            y: None,
            width: None,
            height: None,
            result: None,
        }
    }

    /// Validates attributes and returns the `BoundsBuilder` for bounds computation.
    #[inline]
    fn get_bounds(&self, ctx: &FilterContext) -> Result<BoundsBuilder, FilterError> {
        // With ObjectBoundingBox, only fractions and percents are allowed.
        if ctx.primitive_units() == CoordUnits::ObjectBoundingBox {
            check_units(self.x)?;
            check_units(self.y)?;
            check_units(self.width)?;
            check_units(self.height)?;
        }

        let transform = ctx.paffine();
        if !transform.is_invertible() {
            return Err(FilterError::InvalidParameter(
                "transform is not invertible".to_string(),
            ));
        }

        Ok(BoundsBuilder::new(
            self.x,
            self.y,
            self.width,
            self.height,
            transform,
        ))
    }
}

fn check_units<N: Normalize, V: Validate>(
    length: Option<CssLength<N, V>>,
) -> Result<(), FilterError> {
    match length {
        Some(l) if l.unit == LengthUnit::Px || l.unit == LengthUnit::Percent => Ok(()),
        Some(_) => Err(FilterError::InvalidUnits),
        None => Ok(()),
    }
}

impl Primitive {
    fn parse_standard_attributes(
        &mut self,
        attrs: &Attributes,
    ) -> Result<(Input, Input), ElementError> {
        let mut input_1 = Input::Unspecified;
        let mut input_2 = Input::Unspecified;

        for (attr, value) in attrs.iter() {
            match attr.expanded() {
                expanded_name!("", "x") => self.x = attr.parse(value)?,
                expanded_name!("", "y") => self.y = attr.parse(value)?,
                expanded_name!("", "width") => self.width = attr.parse(value)?,
                expanded_name!("", "height") => self.height = attr.parse(value)?,
                expanded_name!("", "result") => self.result = attr.parse(value)?,
                expanded_name!("", "in") => input_1 = attr.parse(value)?,
                expanded_name!("", "in2") => input_2 = attr.parse(value)?,
                _ => (),
            }
        }

        Ok((input_1, input_2))
    }

    pub fn parse_no_inputs(&mut self, attrs: &Attributes) -> ElementResult {
        let (_, _) = self.parse_standard_attributes(attrs)?;
        Ok(())
    }

    pub fn parse_one_input(&mut self, attrs: &Attributes) -> Result<Input, ElementError> {
        let (input_1, _) = self.parse_standard_attributes(attrs)?;
        Ok(input_1)
    }

    pub fn parse_two_inputs(&mut self, attrs: &Attributes) -> Result<(Input, Input), ElementError> {
        self.parse_standard_attributes(attrs)
    }
}

/// Applies a filter and returns the resulting surface.
pub fn render(
    filter_node: &Node,
    computed_from_node_being_filtered: &ComputedValues,
    source_surface: SharedImageSurface,
    acquired_nodes: &mut AcquiredNodes<'_>,
    draw_ctx: &mut DrawingCtx,
    transform: Transform,
    node_bbox: BoundingBox,
) -> Result<SharedImageSurface, RenderingError> {
    let filter_node = &*filter_node;
    assert!(is_element_of_type!(filter_node, Filter));

    if filter_node.borrow_element().is_in_error() {
        return Ok(source_surface);
    }

    let filter = borrow_element_as!(filter_node, Filter);
    let mut filter_ctx = FilterContext::new(
        &filter,
        computed_from_node_being_filtered,
        source_surface,
        draw_ctx,
        transform,
        node_bbox,
    );

    // If paffine is non-invertible, we won't draw anything. Also bbox combining in bounds
    // computations will panic due to non-invertible martrix.
    if !filter_ctx.paffine().is_invertible() {
        return Ok(filter_ctx.into_output()?);
    }

    let primitives = filter_node
        .children()
        .filter(|c| c.is_element())
        // Skip nodes in error.
        .filter(|c| {
            let in_error = c.borrow_element().is_in_error();

            if in_error {
                rsvg_log!("(ignoring filter primitive {} because it is in error)", c);
            }

            !in_error
        })
        // Keep only filter primitives (those that implement the Filter trait)
        .filter(|c| c.borrow_element().as_filter_effect().is_some());

    for c in primitives {
        let elt = c.borrow_element();
        let filter = elt.as_filter_effect().unwrap();

        let start = Instant::now();

        if let Err(err) = filter
            .render(&c, &filter_ctx, acquired_nodes, draw_ctx)
            .and_then(|result| filter_ctx.store_result(result))
        {
            rsvg_log!("(filter primitive {} returned an error: {})", c, err);

            // Exit early on Cairo errors. Continue rendering otherwise.
            if let FilterError::CairoError(status) = err {
                return Err(RenderingError::from(status));
            }
        }

        let elapsed = start.elapsed();
        rsvg_log!(
            "(rendered filter primitive {} in\n    {} seconds)",
            c,
            elapsed.as_secs() as f64 + f64::from(elapsed.subsec_nanos()) / 1e9
        );
    }

    Ok(filter_ctx.into_output()?)
}

impl From<ColorInterpolationFilters> for SurfaceType {
    fn from(c: ColorInterpolationFilters) -> Self {
        match c {
            ColorInterpolationFilters::LinearRgb => SurfaceType::LinearRgb,
            _ => SurfaceType::SRgb,
        }
    }
}
<commit_msg>New enum PrimitiveParams, with the resolved parameters for each filter primitive<commit_after>//! Entry point for the CSS filters infrastructure.

use cssparser::{BasicParseError, Parser};
use markup5ever::{expanded_name, local_name, namespace_url, ns};
use std::time::Instant;

use crate::bbox::BoundingBox;
use crate::coord_units::CoordUnits;
use crate::document::AcquiredNodes;
use crate::drawing_ctx::DrawingCtx;
use crate::element::{Draw, ElementResult, SetAttributes};
use crate::error::{ElementError, ParseError, RenderingError};
use crate::length::*;
use crate::node::{Node, NodeBorrow};
use crate::parsers::{CustomIdent, Parse, ParseValue};
use crate::properties::ComputedValues;
use crate::property_defs::ColorInterpolationFilters;
use crate::surface_utils::shared_surface::{SharedImageSurface, SurfaceType};
use crate::transform::Transform;
use crate::xml::Attributes;

mod bounds;
use self::bounds::BoundsBuilder;

pub mod context;
use self::context::{FilterContext, FilterResult};

mod error;
use self::error::FilterError;

/// Trait to render filter effect primitives.
pub trait FilterRender {
    /// Renders this filter primitive.
    ///
    /// If this filter primitive can't be rendered for whatever reason (for instance, a required
    /// property hasn't been provided), an error is returned.
    fn render(
        &self,
        node: &Node,
        ctx: &FilterContext,
        acquired_nodes: &mut AcquiredNodes<'_>,
        draw_ctx: &mut DrawingCtx,
    ) -> Result<FilterResult, FilterError>;
}

/// A filter primitive interface.
pub trait FilterEffect: SetAttributes + Draw + FilterRender {}

// Filter Effects do not need to draw themselves
impl<T: FilterEffect> Draw for T {}

pub mod blend;
pub mod color_matrix;
pub mod component_transfer;
pub mod composite;
pub mod convolve_matrix;
pub mod displacement_map;
pub mod flood;
pub mod gaussian_blur;
pub mod image;
pub mod lighting;
pub mod merge;
pub mod morphology;
pub mod offset;
pub mod tile;
pub mod turbulence;

/// Resolved parameters for each filter primitive.
///
/// These gather all the data that a primitive may need during rendering:
/// the `feFoo` element's attributes, any computed values from its properties,
/// and parameters extracted from the element's children (for example,
/// `feMerge` gathers info from its `feMergNode` children).
pub enum PrimitiveParams {
    Blend(Node),
    ColorMatrix(Node),
    ComponentTransfer(Node),
    Composite(Node),
    ConvolveMatrix(Node),
    DiffuseLighting(Node),
    DisplacementMap(Node),
    Flood(Node),
    GaussianBlur(Node),
    Image(Node),
    Merge(Node),
    Morphology(Node),
    Offset(Node),
    SpecularLighting(Node),
    Tile(Node),
    Turbulence(Node),
}

/// The base filter primitive node containing common properties.
struct Primitive {
    x: Option<Length<Horizontal>>,
    y: Option<Length<Vertical>>,
    width: Option<ULength<Horizontal>>,
    height: Option<ULength<Vertical>>,
    result: Option<CustomIdent>,
}

/// An enumeration of possible inputs for a filter primitive.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum Input {
    Unspecified,
    SourceGraphic,
    SourceAlpha,
    BackgroundImage,
    BackgroundAlpha,
    FillPaint,
    StrokePaint,
    FilterOutput(CustomIdent),
}

impl Default for Input {
    fn default() -> Self {
        Input::Unspecified
    }
}

impl Parse for Input {
    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
        parser
            .try_parse(|p| {
                Ok(parse_identifiers!(
                    p,
                    "SourceGraphic" => Input::SourceGraphic,
                    "SourceAlpha" => Input::SourceAlpha,
                    "BackgroundImage" => Input::BackgroundImage,
                    "BackgroundAlpha" => Input::BackgroundAlpha,
                    "FillPaint" => Input::FillPaint,
                    "StrokePaint" => Input::StrokePaint,
                )?)
            })
            .or_else(|_: BasicParseError<'_>| {
                let ident = CustomIdent::parse(parser)?;
                Ok(Input::FilterOutput(ident))
            })
    }
}

impl Primitive {
    /// Constructs a new `Primitive` with empty properties.
    #[inline]
    fn new() -> Primitive {
        Primitive {
            x: None,
            y: None,
            width: None,
            height: None,
            result: None,
        }
    }

    /// Validates attributes and returns the `BoundsBuilder` for bounds computation.
    #[inline]
    fn get_bounds(&self, ctx: &FilterContext) -> Result<BoundsBuilder, FilterError> {
        // With ObjectBoundingBox, only fractions and percents are allowed.
        if ctx.primitive_units() == CoordUnits::ObjectBoundingBox {
            check_units(self.x)?;
            check_units(self.y)?;
            check_units(self.width)?;
            check_units(self.height)?;
        }

        let transform = ctx.paffine();
        if !transform.is_invertible() {
            return Err(FilterError::InvalidParameter(
                "transform is not invertible".to_string(),
            ));
        }

        Ok(BoundsBuilder::new(
            self.x,
            self.y,
            self.width,
            self.height,
            transform,
        ))
    }
}

fn check_units<N: Normalize, V: Validate>(
    length: Option<CssLength<N, V>>,
) -> Result<(), FilterError> {
    match length {
        Some(l) if l.unit == LengthUnit::Px || l.unit == LengthUnit::Percent => Ok(()),
        Some(_) => Err(FilterError::InvalidUnits),
        None => Ok(()),
    }
}

impl Primitive {
    fn parse_standard_attributes(
        &mut self,
        attrs: &Attributes,
    ) -> Result<(Input, Input), ElementError> {
        let mut input_1 = Input::Unspecified;
        let mut input_2 = Input::Unspecified;

        for (attr, value) in attrs.iter() {
            match attr.expanded() {
                expanded_name!("", "x") => self.x = attr.parse(value)?,
                expanded_name!("", "y") => self.y = attr.parse(value)?,
                expanded_name!("", "width") => self.width = attr.parse(value)?,
                expanded_name!("", "height") => self.height = attr.parse(value)?,
                expanded_name!("", "result") => self.result = attr.parse(value)?,
                expanded_name!("", "in") => input_1 = attr.parse(value)?,
                expanded_name!("", "in2") => input_2 = attr.parse(value)?,
                _ => (),
            }
        }

        Ok((input_1, input_2))
    }

    pub fn parse_no_inputs(&mut self, attrs: &Attributes) -> ElementResult {
        let (_, _) = self.parse_standard_attributes(attrs)?;
        Ok(())
    }

    pub fn parse_one_input(&mut self, attrs: &Attributes) -> Result<Input, ElementError> {
        let (input_1, _) = self.parse_standard_attributes(attrs)?;
        Ok(input_1)
    }

    pub fn parse_two_inputs(&mut self, attrs: &Attributes) -> Result<(Input, Input), ElementError> {
        self.parse_standard_attributes(attrs)
    }
}

/// Applies a filter and returns the resulting surface.
pub fn render(
    filter_node: &Node,
    computed_from_node_being_filtered: &ComputedValues,
    source_surface: SharedImageSurface,
    acquired_nodes: &mut AcquiredNodes<'_>,
    draw_ctx: &mut DrawingCtx,
    transform: Transform,
    node_bbox: BoundingBox,
) -> Result<SharedImageSurface, RenderingError> {
    let filter_node = &*filter_node;
    assert!(is_element_of_type!(filter_node, Filter));

    if filter_node.borrow_element().is_in_error() {
        return Ok(source_surface);
    }

    let filter = borrow_element_as!(filter_node, Filter);
    let mut filter_ctx = FilterContext::new(
        &filter,
        computed_from_node_being_filtered,
        source_surface,
        draw_ctx,
        transform,
        node_bbox,
    );

    // If paffine is non-invertible, we won't draw anything. Also bbox combining in bounds
    // computations will panic due to non-invertible martrix.
    if !filter_ctx.paffine().is_invertible() {
        return Ok(filter_ctx.into_output()?);
    }

    let primitives = filter_node
        .children()
        .filter(|c| c.is_element())
        // Skip nodes in error.
        .filter(|c| {
            let in_error = c.borrow_element().is_in_error();

            if in_error {
                rsvg_log!("(ignoring filter primitive {} because it is in error)", c);
            }

            !in_error
        })
        // Keep only filter primitives (those that implement the Filter trait)
        .filter(|c| c.borrow_element().as_filter_effect().is_some());

    for c in primitives {
        let elt = c.borrow_element();
        let filter = elt.as_filter_effect().unwrap();

        let start = Instant::now();

        if let Err(err) = filter
            .render(&c, &filter_ctx, acquired_nodes, draw_ctx)
            .and_then(|result| filter_ctx.store_result(result))
        {
            rsvg_log!("(filter primitive {} returned an error: {})", c, err);

            // Exit early on Cairo errors. Continue rendering otherwise.
            if let FilterError::CairoError(status) = err {
                return Err(RenderingError::from(status));
            }
        }

        let elapsed = start.elapsed();
        rsvg_log!(
            "(rendered filter primitive {} in\n    {} seconds)",
            c,
            elapsed.as_secs() as f64 + f64::from(elapsed.subsec_nanos()) / 1e9
        );
    }

    Ok(filter_ctx.into_output()?)
}

impl From<ColorInterpolationFilters> for SurfaceType {
    fn from(c: ColorInterpolationFilters) -> Self {
        match c {
            ColorInterpolationFilters::LinearRgb => SurfaceType::LinearRgb,
            _ => SurfaceType::SRgb,
        }
    }
}
<|endoftext|>


<commit_before>
use std::collections::HashMap;
use std::sync::Arc;

use super::chaosgame::barnsleyfern;
use super::chaosgame::sierpinski::SierpinskiChaosGame;
use super::curves::cesaro::CesaroFractal;
use super::curves::cesarotri::CesaroTriFractal;
use super::curves::dragon::DragonFractal;
use super::curves::kochcurve::KochCurve;
use super::curves::levyccurve::LevyCCurve;
use super::curves::terdragon::TerdragonFractal;
use super::escapetime::burningship::BurningShip;
use super::escapetime::mandelbrot::Mandelbrot;
use super::lindenmayer::LindenmayerSystemTurtleProgram;
use super::pistonrendering::WindowHandler;
use super::pistonrendering;

/// Print a message to stderr and exit with a non-zero exit code
macro_rules! abort {
    ( $($arg:tt)* ) => {
        {
            use std::io::{stderr, Write};
            use std;
            writeln!(&mut stderr(), $($arg)*).unwrap();
            std::process::exit(1);
        }
    }
}

pub struct Arguments {
    pub curve: String,
    pub iterations: u64,
    pub drawrate: u64,
    pub power: u64,
}

pub struct FractalData {
    pub name: &'static str,
    pub desc: &'static str,
    pub args: &'static [&'static str],
    /// Function or closure that takes in a list of arguments and constructs a WindowHandler for
    /// the curve. It should then call the passed in function pointer and give it the
    /// WindowHandler.
    pub with_window_handler: &'static (Fn(&Arguments, &Fn(&mut WindowHandler)) + Sync),
}

impl FractalData {
    pub fn print_info(&self, dollar_zero: &str) {
        println!("{}", self.name);
        println!("{}", self.desc);
        print!("Usage: {} {}", dollar_zero, self.name);
        for arg in self.args {
            print!(" {}", arg);
        }
        println!("");
    }
}

static BARNSLEYFERN: FractalData = FractalData {
    name: "barnsleyfern",
    desc: "Draws the Barnsley Fern fractal using a chaos game with affine transforms.",
    args: &[],
    with_window_handler: &|args, runner| {
        let game = Arc::new(barnsleyfern::BarnsleyFern::new(&barnsleyfern::REFERENCE_TRANSFORMS,
                                                            &barnsleyfern::REFERENCE_WEIGHTS));
        let mut handler =
            pistonrendering::chaosgame::ChaosGameWindowHandler::new(game, args.drawrate as usize);
        runner(&mut handler);
    },
};

static BURNINGSHIP: FractalData = FractalData {
    name: "burningship",
    desc: "Draws the burning ship fractal",
    args: &["MAX_ITERATIONS", "POWER"],
    with_window_handler: &|args, runner| {
        if args.iterations < 1 {
            abort!("Must specify a MAX_ITERATIONS of 1 or greater!");
        }
        let burningship = BurningShip::new(args.iterations, args.power);
        let mut handler = pistonrendering::escapetime::EscapeTimeWindowHandler::new(&burningship);
        runner(&mut handler);
    },
};

static CESARO: FractalData = FractalData {
    name: "cesaro",
    desc: "Draws a square Césaro fractal. Needs an ITERATION.",
    args: &["ITERATION"],
    with_window_handler: &|args, runner| {
        let program = LindenmayerSystemTurtleProgram::new(CesaroFractal::new(args.iterations));
        let mut handler = pistonrendering::turtle::construct_turtle_window_handler(&program,
                                                                                args.drawrate);
        runner(&mut *handler);
    },
};

static CESAROTRI: FractalData = FractalData {
    name: "cesarotri",
    desc: "Draws a triangle Césaro fractal. Needs an ITERATION.",
    args: &["ITERATION"],
    with_window_handler: &|args, runner| {
        let program = LindenmayerSystemTurtleProgram::new(CesaroTriFractal::new(args.iterations));
        let mut handler = pistonrendering::turtle::construct_turtle_window_handler(&program,
                                                                                args.drawrate);
        runner(&mut *handler);
    },
};

static DRAGON: FractalData = FractalData {
    name: "dragon",
    desc: "Draws dragon curve fractal. Needs an ITERATION.",
    args: &["ITERATION"],
    with_window_handler: &|args, runner| {
        let program = DragonFractal::new(args.iterations);
        let mut handler = pistonrendering::turtle::construct_turtle_window_handler(&program,
                                                                                args.drawrate);
        runner(&mut *handler);
    },
};

static KOCHCURVE: FractalData = FractalData {
    name: "kochcurve",
    desc: "Draws a Koch snowflake. Needs an ITERATION.",
    args: &["ITERATION"],
    with_window_handler: &|args, runner| {
        let program = LindenmayerSystemTurtleProgram::new(KochCurve::new(args.iterations));
        let mut handler = pistonrendering::turtle::construct_turtle_window_handler(&program,
                                                                                args.drawrate);
        runner(&mut *handler);
    },
};

static LEVYCCURVE: FractalData = FractalData {
    name: "levyccurve",
    desc: "Draws a Levy C Curve. Needs an ITERATION.",
    args: &["ITERATION"],
    with_window_handler: &|args, runner| {
        let program = LindenmayerSystemTurtleProgram::new(LevyCCurve::new(args.iterations));
        let mut handler = pistonrendering::turtle::construct_turtle_window_handler(&program,
                                                                                args.drawrate);
        runner(&mut *handler);
    },
};

static MANDELBROT: FractalData = FractalData {
    name: "mandelbrot",
    desc: "Draws the traditional mandelbrot fractal",
    args: &["MAX_ITERATIONS", "POWER"],
    with_window_handler: &|args, runner| {
        if args.iterations < 1 {
            abort!("Must specify a MAX_ITERATIONS of 1 or greater!");
        }
        let mandelbrot = Mandelbrot::new(args.iterations, args.power);
        let mut handler = pistonrendering::escapetime::EscapeTimeWindowHandler::new(&mandelbrot);
        runner(&mut handler);
    },
};

static SIERPINSKI: FractalData = FractalData {
    name: "sierpinski",
    desc: "Draws a Sierpinski triangle using a chaos game. It randomly picks 3 points on the \
           screen as the vertices of the triangle, finds the center of the triangle, and then \
           moves halfway to a random vertex. Repeat ad nauseum.",
    args: &["foo"],
    with_window_handler: &|args, runner| {
        let game = Arc::new(SierpinskiChaosGame::new());
        let mut handler =
            pistonrendering::chaosgame::ChaosGameWindowHandler::new(game, args.drawrate as usize);
        runner(&mut handler);
    },
};

static TERDRAGON: FractalData = FractalData {
    name: "terdragon",
    desc: "Draws a terdragon curve. Needs an ITERATION.",
    args: &["ITERATION"],
    with_window_handler: &|args, runner| {
        let program = LindenmayerSystemTurtleProgram::new(TerdragonFractal::new(args.iterations));
        let mut handler = pistonrendering::turtle::construct_turtle_window_handler(&program,
                                                                                args.drawrate);
        runner(&mut *handler);
    },
};

pub fn get_chaos_data() -> HashMap<&'static str, &'static FractalData> {
    let mut data = HashMap::new();
    data.insert("barnsleyfern", &BARNSLEYFERN);
    data.insert("burningship", &BURNINGSHIP);
    data.insert("cesaro", &CESARO);
    data.insert("cesarotri", &CESAROTRI);
    data.insert("dragon", &DRAGON);
    data.insert("kochcurve", &KOCHCURVE);
    data.insert("levyccurve", &LEVYCCURVE);
    data.insert("mandelbrot", &MANDELBROT);
    data.insert("sierpinski", &SIERPINSKI);
    data.insert("terdragon", &TERDRAGON);
    data
}
<commit_msg>Adding a mixing copyright notice<commit_after>// Copyright (c) 2016 William (B.J.) Snow Orvis
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Data structures describing each fractal or curve that the program can draw.
//!
//! The data structures also provide a closure to process configuration for
//! each curve and configure a WindowHandler to handle callbacks from the event
//! loop.

use std::collections::HashMap;
use std::sync::Arc;

use super::chaosgame::barnsleyfern;
use super::chaosgame::sierpinski::SierpinskiChaosGame;
use super::curves::cesaro::CesaroFractal;
use super::curves::cesarotri::CesaroTriFractal;
use super::curves::dragon::DragonFractal;
use super::curves::kochcurve::KochCurve;
use super::curves::levyccurve::LevyCCurve;
use super::curves::terdragon::TerdragonFractal;
use super::escapetime::burningship::BurningShip;
use super::escapetime::mandelbrot::Mandelbrot;
use super::lindenmayer::LindenmayerSystemTurtleProgram;
use super::pistonrendering::WindowHandler;
use super::pistonrendering;

/// Print a message to stderr and exit with a non-zero exit code
macro_rules! abort {
    ( $($arg:tt)* ) => {
        {
            use std::io::{stderr, Write};
            use std;
            writeln!(&mut stderr(), $($arg)*).unwrap();
            std::process::exit(1);
        }
    }
}

pub struct Arguments {
    pub curve: String,
    pub iterations: u64,
    pub drawrate: u64,
    pub power: u64,
}

pub struct FractalData {
    pub name: &'static str,
    pub desc: &'static str,
    pub args: &'static [&'static str],
    /// Function or closure that takes in a list of arguments and constructs a WindowHandler for
    /// the curve. It should then call the passed in function pointer and give it the
    /// WindowHandler.
    pub with_window_handler: &'static (Fn(&Arguments, &Fn(&mut WindowHandler)) + Sync),
}

impl FractalData {
    pub fn print_info(&self, dollar_zero: &str) {
        println!("{}", self.name);
        println!("{}", self.desc);
        print!("Usage: {} {}", dollar_zero, self.name);
        for arg in self.args {
            print!(" {}", arg);
        }
        println!("");
    }
}

static BARNSLEYFERN: FractalData = FractalData {
    name: "barnsleyfern",
    desc: "Draws the Barnsley Fern fractal using a chaos game with affine transforms.",
    args: &[],
    with_window_handler: &|args, runner| {
        let game = Arc::new(barnsleyfern::BarnsleyFern::new(&barnsleyfern::REFERENCE_TRANSFORMS,
                                                            &barnsleyfern::REFERENCE_WEIGHTS));
        let mut handler =
            pistonrendering::chaosgame::ChaosGameWindowHandler::new(game, args.drawrate as usize);
        runner(&mut handler);
    },
};

static BURNINGSHIP: FractalData = FractalData {
    name: "burningship",
    desc: "Draws the burning ship fractal",
    args: &["MAX_ITERATIONS", "POWER"],
    with_window_handler: &|args, runner| {
        if args.iterations < 1 {
            abort!("Must specify a MAX_ITERATIONS of 1 or greater!");
        }
        let burningship = BurningShip::new(args.iterations, args.power);
        let mut handler = pistonrendering::escapetime::EscapeTimeWindowHandler::new(&burningship);
        runner(&mut handler);
    },
};

static CESARO: FractalData = FractalData {
    name: "cesaro",
    desc: "Draws a square Césaro fractal. Needs an ITERATION.",
    args: &["ITERATION"],
    with_window_handler: &|args, runner| {
        let program = LindenmayerSystemTurtleProgram::new(CesaroFractal::new(args.iterations));
        let mut handler = pistonrendering::turtle::construct_turtle_window_handler(&program,
                                                                                args.drawrate);
        runner(&mut *handler);
    },
};

static CESAROTRI: FractalData = FractalData {
    name: "cesarotri",
    desc: "Draws a triangle Césaro fractal. Needs an ITERATION.",
    args: &["ITERATION"],
    with_window_handler: &|args, runner| {
        let program = LindenmayerSystemTurtleProgram::new(CesaroTriFractal::new(args.iterations));
        let mut handler = pistonrendering::turtle::construct_turtle_window_handler(&program,
                                                                                args.drawrate);
        runner(&mut *handler);
    },
};

static DRAGON: FractalData = FractalData {
    name: "dragon",
    desc: "Draws dragon curve fractal. Needs an ITERATION.",
    args: &["ITERATION"],
    with_window_handler: &|args, runner| {
        let program = DragonFractal::new(args.iterations);
        let mut handler = pistonrendering::turtle::construct_turtle_window_handler(&program,
                                                                                args.drawrate);
        runner(&mut *handler);
    },
};

static KOCHCURVE: FractalData = FractalData {
    name: "kochcurve",
    desc: "Draws a Koch snowflake. Needs an ITERATION.",
    args: &["ITERATION"],
    with_window_handler: &|args, runner| {
        let program = LindenmayerSystemTurtleProgram::new(KochCurve::new(args.iterations));
        let mut handler = pistonrendering::turtle::construct_turtle_window_handler(&program,
                                                                                args.drawrate);
        runner(&mut *handler);
    },
};

static LEVYCCURVE: FractalData = FractalData {
    name: "levyccurve",
    desc: "Draws a Levy C Curve. Needs an ITERATION.",
    args: &["ITERATION"],
    with_window_handler: &|args, runner| {
        let program = LindenmayerSystemTurtleProgram::new(LevyCCurve::new(args.iterations));
        let mut handler = pistonrendering::turtle::construct_turtle_window_handler(&program,
                                                                                args.drawrate);
        runner(&mut *handler);
    },
};

static MANDELBROT: FractalData = FractalData {
    name: "mandelbrot",
    desc: "Draws the traditional mandelbrot fractal",
    args: &["MAX_ITERATIONS", "POWER"],
    with_window_handler: &|args, runner| {
        if args.iterations < 1 {
            abort!("Must specify a MAX_ITERATIONS of 1 or greater!");
        }
        let mandelbrot = Mandelbrot::new(args.iterations, args.power);
        let mut handler = pistonrendering::escapetime::EscapeTimeWindowHandler::new(&mandelbrot);
        runner(&mut handler);
    },
};

static SIERPINSKI: FractalData = FractalData {
    name: "sierpinski",
    desc: "Draws a Sierpinski triangle using a chaos game. It randomly picks 3 points on the \
           screen as the vertices of the triangle, finds the center of the triangle, and then \
           moves halfway to a random vertex. Repeat ad nauseum.",
    args: &["foo"],
    with_window_handler: &|args, runner| {
        let game = Arc::new(SierpinskiChaosGame::new());
        let mut handler =
            pistonrendering::chaosgame::ChaosGameWindowHandler::new(game, args.drawrate as usize);
        runner(&mut handler);
    },
};

static TERDRAGON: FractalData = FractalData {
    name: "terdragon",
    desc: "Draws a terdragon curve. Needs an ITERATION.",
    args: &["ITERATION"],
    with_window_handler: &|args, runner| {
        let program = LindenmayerSystemTurtleProgram::new(TerdragonFractal::new(args.iterations));
        let mut handler = pistonrendering::turtle::construct_turtle_window_handler(&program,
                                                                                args.drawrate);
        runner(&mut *handler);
    },
};

pub fn get_chaos_data() -> HashMap<&'static str, &'static FractalData> {
    let mut data = HashMap::new();
    data.insert("barnsleyfern", &BARNSLEYFERN);
    data.insert("burningship", &BURNINGSHIP);
    data.insert("cesaro", &CESARO);
    data.insert("cesarotri", &CESAROTRI);
    data.insert("dragon", &DRAGON);
    data.insert("kochcurve", &KOCHCURVE);
    data.insert("levyccurve", &LEVYCCURVE);
    data.insert("mandelbrot", &MANDELBROT);
    data.insert("sierpinski", &SIERPINSKI);
    data.insert("terdragon", &TERDRAGON);
    data
}
<|endoftext|>


<commit_before>use std::ptr::null;
use libc::{c_int, c_char};
use sync::mutex::{StaticMutex, MUTEX_INIT};

#[link(name="gdal")]
extern {
    fn OGRRegisterAll();
    fn OGROpen(pszName: *c_char, bUpdate: c_int, pahDriverList: *()) -> *();
    fn OGR_DS_GetLayerCount(OGRDataSourceH: *()) -> c_int;
    fn OGR_DS_Destroy(OGRDataSourceH: *());
    fn OGR_DS_GetLayer(OGRDataSourceH: *(), iLayer: c_int) -> *();
    fn OGR_L_GetNextFeature(hLayer: *()) -> *();
    fn OGR_F_Destroy(hFeat: *());
}

static mut LOCK: StaticMutex = MUTEX_INIT;
static mut registered_drivers: bool = false;

fn register_drivers() {
    unsafe {
        let _g = LOCK.lock();
        if ! registered_drivers {
            OGRRegisterAll();
            registered_drivers = true;
        }
    }
}

pub struct VectorDataset {
    c_dataset: *(),
}

impl VectorDataset {
    pub fn get_layer_count(&self) -> int {
        return unsafe { OGR_DS_GetLayerCount(self.c_dataset) } as int;
    }

    pub fn get_layer<'a>(&'a self, idx: int) -> Option<Layer<'a>> {
        let c_layer = unsafe { OGR_DS_GetLayer(self.c_dataset, idx as c_int) };
        return match c_layer.is_null() {
            true  => None,
            false => Some(Layer{vector_dataset: self, c_layer: c_layer}),
        };
    }
}

impl Drop for VectorDataset {
    fn drop(&mut self) {
        unsafe { OGR_DS_Destroy(self.c_dataset); }
    }
}

pub struct Layer<'a> {
    vector_dataset: &'a VectorDataset,
    c_layer: *(),
}

impl<'a> Layer<'a> {
    pub fn features<'a>(&'a self) -> FeatureIterator<'a> {
        return FeatureIterator{layer: self};
    }
}

pub struct FeatureIterator<'a> {
    layer: &'a Layer<'a>,
}

impl<'a> Iterator<Feature> for FeatureIterator<'a> {
    #[inline]
    fn next(&mut self) -> Option<Feature> {
        let c_feature = unsafe { OGR_L_GetNextFeature(self.layer.c_layer) };
        return match c_feature.is_null() {
            true  => None,
            false => Some(Feature{c_feature: c_feature}),
        };
    }
}

pub struct Feature {
    c_feature: *(),
}

impl Drop for Feature {
    fn drop(&mut self) {
        unsafe { OGR_F_Destroy(self.c_feature); }
    }
}

pub fn open(path: &Path) -> Option<VectorDataset> {
    register_drivers();
    let filename = path.as_str().unwrap();
    let c_dataset = filename.with_c_str(|c_filename| {
        return unsafe { OGROpen(c_filename, 0, null()) };
    });
    return match c_dataset.is_null() {
        true  => None,
        false => Some(VectorDataset{c_dataset: c_dataset}),
    };
}

#[cfg(test)]
mod test {
    use std::os::getenv;
    use std::path::Path;
    use super::{Feature, open};

    fn fixture_path(name: &str) -> Path {
        let envvar = "RUST_GDAL_TEST_FIXTURES";
        let fixtures = match getenv(envvar) {
            Some(p) => Path::new(p),
            None => fail!("Environment variable {} not set", envvar)
        };
        let rv = fixtures.join(name);
        return rv;
    }

    #[test]
    fn test_layer_count() {
        let ds = open(&fixture_path("roads.geojson")).unwrap();
        assert_eq!(ds.get_layer_count(), 1);
    }

    #[test]
    fn test_iterate_features() {
        let ds = open(&fixture_path("roads.geojson")).unwrap();
        let layer = ds.get_layer(0).unwrap();
        let features: Vec<Feature> = layer.features().collect();
        assert_eq!(features.len(), 21);
    }
}
<commit_msg>fix argument names<commit_after>use std::ptr::null;
use libc::{c_int, c_char};
use sync::mutex::{StaticMutex, MUTEX_INIT};

#[link(name="gdal")]
extern {
    fn OGRRegisterAll();
    fn OGROpen(pszName: *c_char, bUpdate: c_int, pahDriverList: *()) -> *();
    fn OGR_DS_GetLayerCount(hDS: *()) -> c_int;
    fn OGR_DS_Destroy(hDataSource: *());
    fn OGR_DS_GetLayer(hDS: *(), iLayer: c_int) -> *();
    fn OGR_L_GetNextFeature(hLayer: *()) -> *();
    fn OGR_F_Destroy(hFeat: *());
}

static mut LOCK: StaticMutex = MUTEX_INIT;
static mut registered_drivers: bool = false;

fn register_drivers() {
    unsafe {
        let _g = LOCK.lock();
        if ! registered_drivers {
            OGRRegisterAll();
            registered_drivers = true;
        }
    }
}

pub struct VectorDataset {
    c_dataset: *(),
}

impl VectorDataset {
    pub fn get_layer_count(&self) -> int {
        return unsafe { OGR_DS_GetLayerCount(self.c_dataset) } as int;
    }

    pub fn get_layer<'a>(&'a self, idx: int) -> Option<Layer<'a>> {
        let c_layer = unsafe { OGR_DS_GetLayer(self.c_dataset, idx as c_int) };
        return match c_layer.is_null() {
            true  => None,
            false => Some(Layer{vector_dataset: self, c_layer: c_layer}),
        };
    }
}

impl Drop for VectorDataset {
    fn drop(&mut self) {
        unsafe { OGR_DS_Destroy(self.c_dataset); }
    }
}

pub struct Layer<'a> {
    vector_dataset: &'a VectorDataset,
    c_layer: *(),
}

impl<'a> Layer<'a> {
    pub fn features<'a>(&'a self) -> FeatureIterator<'a> {
        return FeatureIterator{layer: self};
    }
}

pub struct FeatureIterator<'a> {
    layer: &'a Layer<'a>,
}

impl<'a> Iterator<Feature> for FeatureIterator<'a> {
    #[inline]
    fn next(&mut self) -> Option<Feature> {
        let c_feature = unsafe { OGR_L_GetNextFeature(self.layer.c_layer) };
        return match c_feature.is_null() {
            true  => None,
            false => Some(Feature{c_feature: c_feature}),
        };
    }
}

pub struct Feature {
    c_feature: *(),
}

impl Drop for Feature {
    fn drop(&mut self) {
        unsafe { OGR_F_Destroy(self.c_feature); }
    }
}

pub fn open(path: &Path) -> Option<VectorDataset> {
    register_drivers();
    let filename = path.as_str().unwrap();
    let c_dataset = filename.with_c_str(|c_filename| {
        return unsafe { OGROpen(c_filename, 0, null()) };
    });
    return match c_dataset.is_null() {
        true  => None,
        false => Some(VectorDataset{c_dataset: c_dataset}),
    };
}

#[cfg(test)]
mod test {
    use std::os::getenv;
    use std::path::Path;
    use super::{Feature, open};

    fn fixture_path(name: &str) -> Path {
        let envvar = "RUST_GDAL_TEST_FIXTURES";
        let fixtures = match getenv(envvar) {
            Some(p) => Path::new(p),
            None => fail!("Environment variable {} not set", envvar)
        };
        let rv = fixtures.join(name);
        return rv;
    }

    #[test]
    fn test_layer_count() {
        let ds = open(&fixture_path("roads.geojson")).unwrap();
        assert_eq!(ds.get_layer_count(), 1);
    }

    #[test]
    fn test_iterate_features() {
        let ds = open(&fixture_path("roads.geojson")).unwrap();
        let layer = ds.get_layer(0).unwrap();
        let features: Vec<Feature> = layer.features().collect();
        assert_eq!(features.len(), 21);
    }
}
<|endoftext|>