<commit_before>use gl;
use gl::types::GLuint;
use libc::c_void;

use image;
use image::GenericImage;

use graphics::ImageSize;

/// Wraps OpenGL texture data.
/// The texture gets deleted when running out of scope.
///
/// In order to create a texture the function `GenTextures` must be loaded.
/// This is done automatically by the window back-ends in Piston.
pub struct Texture {
    id: GLuint,
    width: u32,
    height: u32,
}

impl Texture {
    /// Creates a new texture.
    #[inline(always)]
    pub fn new(id: GLuint, width: u32, height: u32) -> Texture {
        Texture {
            id: id,
            width: width,
            height: height,
        }
    }

    /// Gets the OpenGL id of the texture.
    #[inline(always)]
    pub fn get_id(&self) -> GLuint {
        self.id
    }

    /// Loads image from memory, the format is 8-bit greyscale.
    pub fn from_memory_alpha(buf: &[u8], width: u32, height: u32) -> Result<Texture, String> {
        let mut pixels = Vec::new();
        for alpha in buf.iter() {
            pixels.push(255);
            pixels.push(255);
            pixels.push(255);
            pixels.push(*alpha);
        }

        let mut id: GLuint = 0;
        unsafe {
            gl::GenTextures(1, &mut id);
            gl::BindTexture(gl::TEXTURE_2D, id);
            gl::TexParameteri(
                gl::TEXTURE_2D,
                gl::TEXTURE_MIN_FILTER,
                gl::LINEAR as i32
            );
            gl::TexParameteri(
                gl::TEXTURE_2D,
                gl::TEXTURE_MAG_FILTER,
                gl::LINEAR as i32
            );
            gl::TexImage2D(
                gl::TEXTURE_2D,
                0,
                gl::RGBA as i32,
                width as i32,
                height as i32,
                0,
                gl::RGBA,
                gl::UNSIGNED_BYTE,
                pixels.as_ptr() as *const c_void
            );
        }

        Ok(Texture::new(id, width, height))
    }

    /// Loads image by relative file name to the asset root.
    pub fn from_path(path: &Path) -> Result<Texture, String> {
        let img = match image::open(path) {
            Ok(img) => img,
            Err(e)  => return Err(format!("Could not load '{}': {}",
                path.filename_str().unwrap(), e)),
        };

        match img.color() {
            image::RGBA(8) => {},
            c => fail!("Unsupported color type {} in png", c),
        };

        let (width, height) = img.dimensions();

        let mut id: GLuint = 0;
        unsafe {
            gl::GenTextures(1, &mut id);
            gl::BindTexture(gl::TEXTURE_2D, id);
            gl::TexParameteri(
                gl::TEXTURE_2D,
                gl::TEXTURE_MIN_FILTER,
                gl::LINEAR as i32
            );
            gl::TexParameteri(
                gl::TEXTURE_2D,
                gl::TEXTURE_MAG_FILTER,
                gl::LINEAR as i32
            );
            gl::TexImage2D(
                gl::TEXTURE_2D,
                0,
                gl::RGBA as i32,
                width as i32,
                height as i32,
                0,
                gl::RGBA,
                gl::UNSIGNED_BYTE,
                img.raw_pixels().as_ptr() as *const c_void
            );
        }

        Ok(Texture::new(id, width, height))
    }
}

impl Drop for Texture {
    fn drop(&mut self) {
        unsafe {
            gl::DeleteTextures(1, [self.id].as_ptr());
        }
    }
}

impl ImageSize for Texture {
    fn get_size(&self) -> (u32, u32) {
        (self.width, self.height)
    }
}
<commit_msg>Added update function<commit_after>use gl;
use gl::types::GLuint;
use libc::c_void;

use image;
use image::GenericImage;

use graphics::ImageSize;

/// Wraps OpenGL texture data.
/// The texture gets deleted when running out of scope.
///
/// In order to create a texture the function `GenTextures` must be loaded.
/// This is done automatically by the window back-ends in Piston.
pub struct Texture {
    id: GLuint,
    width: u32,
    height: u32,
}

impl Texture {
    /// Creates a new texture.
    #[inline(always)]
    pub fn new(id: GLuint, width: u32, height: u32) -> Texture {
        Texture {
            id: id,
            width: width,
            height: height,
        }
    }

    /// Gets the OpenGL id of the texture.
    #[inline(always)]
    pub fn get_id(&self) -> GLuint {
        self.id
    }

    /// Loads image from memory, the format is 8-bit greyscale.
    pub fn from_memory_alpha(buf: &[u8], width: u32, height: u32) -> Result<Texture, String> {
        let mut pixels = Vec::new();
        for alpha in buf.iter() {
            pixels.push(255);
            pixels.push(255);
            pixels.push(255);
            pixels.push(*alpha);
        }

        let mut id: GLuint = 0;
        unsafe {
            gl::GenTextures(1, &mut id);
            gl::BindTexture(gl::TEXTURE_2D, id);
            gl::TexParameteri(
                gl::TEXTURE_2D,
                gl::TEXTURE_MIN_FILTER,
                gl::LINEAR as i32
            );
            gl::TexParameteri(
                gl::TEXTURE_2D,
                gl::TEXTURE_MAG_FILTER,
                gl::LINEAR as i32
            );
            gl::TexImage2D(
                gl::TEXTURE_2D,
                0,
                gl::RGBA as i32,
                width as i32,
                height as i32,
                0,
                gl::RGBA,
                gl::UNSIGNED_BYTE,
                pixels.as_ptr() as *const c_void
            );
        }

        Ok(Texture::new(id, width, height))
    }

    /// Loads image by relative file name to the asset root.
    pub fn from_path(path: &Path) -> Result<Texture, String> {
        let img = match image::open(path) {
            Ok(img) => img,
            Err(e)  => return Err(format!("Could not load '{}': {}",
                path.filename_str().unwrap(), e)),
        };

        match img.color() {
            image::RGBA(8) => {},
            c => fail!("Unsupported color type {} in png", c),
        };

        let (width, height) = img.dimensions();

        let mut id: GLuint = 0;
        unsafe {
            gl::GenTextures(1, &mut id);
            gl::BindTexture(gl::TEXTURE_2D, id);
            gl::TexParameteri(
                gl::TEXTURE_2D,
                gl::TEXTURE_MIN_FILTER,
                gl::LINEAR as i32
            );
            gl::TexParameteri(
                gl::TEXTURE_2D,
                gl::TEXTURE_MAG_FILTER,
                gl::LINEAR as i32
            );
            gl::TexImage2D(
                gl::TEXTURE_2D,
                0,
                gl::RGBA as i32,
                width as i32,
                height as i32,
                0,
                gl::RGBA,
                gl::UNSIGNED_BYTE,
                img.raw_pixels().as_ptr() as *const c_void
            );
        }

        Ok(Texture::new(id, width, height))
    }

    /// Updates image with a new one.
    pub fn update(&mut self, img: &image::ImageBuf<image::Rgba<u8>>, width: u32, height: u32) {
        gl::BindTexture(gl::TEXTURE_2D, self.id);
        unsafe {
            gl::TexImage2D(
                gl::TEXTURE_2D,
                0,
                gl::RGBA as i32,
                width as i32,
                height as i32,
                0,
                gl::RGBA,
                gl::UNSIGNED_BYTE,
                img.pixelbuf().as_ptr() as *const c_void
            );
        }
    }
}

impl Drop for Texture {
    fn drop(&mut self) {
        unsafe {
            gl::DeleteTextures(1, [self.id].as_ptr());
        }
    }
}

impl ImageSize for Texture {
    fn get_size(&self) -> (u32, u32) {
        (self.width, self.height)
    }
}
<|endoftext|>


<commit_before>use crate::context::Context;
#[cfg(not(feature = "no_logging"))]
use crate::error::RenderError;
use crate::helpers::{HelperDef, HelperResult};
#[cfg(not(feature = "no_logging"))]
use crate::json::value::JsonRender;
use crate::output::Output;
use crate::registry::Registry;
use crate::render::{Helper, RenderContext};
#[cfg(not(feature = "no_logging"))]
use log::Level;
#[cfg(not(feature = "no_logging"))]
use std::str::FromStr;

#[derive(Clone, Copy)]
pub struct LogHelper;

#[cfg(not(feature = "no_logging"))]
impl HelperDef for LogHelper {
    fn call<'reg: 'rc, 'rc>(
        &self,
        h: &Helper<'reg, 'rc>,
        _: &'reg Registry<'reg>,
        _: &'rc Context,
        _: &mut RenderContext<'reg, 'rc>,
        _: &mut dyn Output,
    ) -> HelperResult {
        let param = h
            .param(0)
            .ok_or_else(|| RenderError::new("Param not found for helper \"log\""))?;
        let level = h
            .hash_get("level")
            .and_then(|v| v.value().as_str())
            .unwrap_or("info");

        if let Ok(log_level) = Level::from_str(level) {
            log!(
                log_level,
                "{}: {}",
                param.relative_path().unwrap_or(&"".to_owned()),
                param.value().render()
            )
        } else {
            return Err(RenderError::new(&format!(
                "Unsupported logging level {}",
                level
            )));
        }
        Ok(())
    }
}

#[cfg(feature = "no_logging")]
impl HelperDef for LogHelper {
    fn call<'reg: 'rc, 'rc>(
        &self,
        _: &Helper<'reg, 'rc>,
        _: &Registry<'reg>,
        _: &Context,
        _: &mut RenderContext<'reg, 'rc>,
        _: &mut dyn Output,
    ) -> HelperResult {
        Ok(())
    }
}

pub static LOG_HELPER: LogHelper = LogHelper;
<commit_msg>(feat) add multi-param support for log helper<commit_after>use crate::context::Context;
#[cfg(not(feature = "no_logging"))]
use crate::error::RenderError;
use crate::helpers::{HelperDef, HelperResult};
#[cfg(not(feature = "no_logging"))]
use crate::json::value::JsonRender;
use crate::output::Output;
use crate::registry::Registry;
use crate::render::{Helper, RenderContext};
#[cfg(not(feature = "no_logging"))]
use log::Level;
#[cfg(not(feature = "no_logging"))]
use std::str::FromStr;

#[derive(Clone, Copy)]
pub struct LogHelper;

#[cfg(not(feature = "no_logging"))]
impl HelperDef for LogHelper {
    fn call<'reg: 'rc, 'rc>(
        &self,
        h: &Helper<'reg, 'rc>,
        _: &'reg Registry<'reg>,
        _: &'rc Context,
        _: &mut RenderContext<'reg, 'rc>,
        _: &mut dyn Output,
    ) -> HelperResult {
        let param_to_log = h
            .params()
            .iter()
            .map(|p| {
                if let Some(ref relative_path) = p.relative_path() {
                    format!("{}: {}", relative_path, p.value().render())
                } else {
                    format!("{}", p.value().render())
                }
            })
            .collect::<Vec<String>>()
            .join(", ");

        let level = h
            .hash_get("level")
            .and_then(|v| v.value().as_str())
            .unwrap_or("info");

        if let Ok(log_level) = Level::from_str(level) {
            log!(log_level, "{}", param_to_log)
        } else {
            return Err(RenderError::new(&format!(
                "Unsupported logging level {}",
                level
            )));
        }
        Ok(())
    }
}

#[cfg(feature = "no_logging")]
impl HelperDef for LogHelper {
    fn call<'reg: 'rc, 'rc>(
        &self,
        _: &Helper<'reg, 'rc>,
        _: &Registry<'reg>,
        _: &Context,
        _: &mut RenderContext<'reg, 'rc>,
        _: &mut dyn Output,
    ) -> HelperResult {
        Ok(())
    }
}

pub static LOG_HELPER: LogHelper = LogHelper;
<|endoftext|>


<commit_before>/* Fundamental layout structures and algorithms. */

use arc = std::arc;
use arc::ARC;
use au = gfx::geometry;
use au::au;
use core::dvec::DVec;
use core::to_str::ToStr;
use core::rand;
use css::styles::SpecifiedStyle;
use css::values::{BoxSizing, Length, Px, CSSDisplay, Specified, BgColor, BgColorTransparent};
use dl = gfx::display_list;
use dom::element::{ElementKind, HTMLDivElement, HTMLImageElement};
use dom::node::{Element, Node, NodeData, NodeKind, NodeTree};
use dom::rcu;
use geom::rect::Rect;
use geom::size::Size2D;
use geom::point::Point2D;
use image::{Image, ImageHolder};
use layout::context::LayoutContext;
use layout::debug::BoxedDebugMethods;
use layout::flow::FlowContext;
use layout::text::TextBoxData;
use servo_text::text_run::TextRun;
use std::net::url::Url;
use task::spawn;
use util::color::Color;
use util::tree;
use vec::{push, push_all};

/**
Render boxes (`struct RenderBox`) are the leafs of the layout
tree. They cannot position themselves. In general, render boxes do not
have a simple correspondence with CSS boxes as in the specification:

 * Several render boxes may correspond to the same CSS box or DOM
   node. For example, a CSS text box broken across two lines is
   represented by two render boxes.

 * Some CSS boxes are not created at all, such as some anonymous block
   boxes induced by inline boxes with block-level sibling boxes. In
   that case, Servo uses an InlineFlow with BlockFlow siblings; the
   InlineFlow is block-level, but not a block container. It is
   positioned as if it were a block box, but its children are
   positioned according to inline flow.

Fundamental box types include:

 * GenericBox: an empty box that contributes only borders, margins,
padding, backgrounds. It is analogous to a CSS nonreplaced content box.

 * ImageBox: a box that represents a (replaced content) image and its
   accompanying borders, shadows, etc.

 * TextBox: a box representing a single run of text with a distinct
   style. A TextBox may be split into two or more render boxes across
   line breaks. Several TextBoxes may correspond to a single DOM text
   node. Split text boxes are implemented by referring to subsets of a
   master TextRun object.

*/

/* A box's kind influences how its styles are interpreted during
   layout.  For example, replaced content such as images are resized
   differently than tables, text, or other content.

   It also holds data specific to different box types, such as text.
*/

struct RenderBoxData {
    /* references to children, parent inline flow boxes  */
    tree : tree::Tree<@RenderBox>,
    /* originating DOM node */
    node : Node,
    /* reference to containing flow context, which this box
       participates in */
    ctx  : @FlowContext,
    /* position of this box relative to owning flow */
    mut position : Rect<au>,
    font_size : Length,
    /* TODO (Issue #87): debug only */
    mut id: int
}

enum RenderBoxType {
    RenderBox_Generic,
    RenderBox_Image,
    RenderBox_Text
}

enum RenderBox {
    GenericBox(RenderBoxData),
    ImageBox(RenderBoxData, ImageHolder),
    TextBox(RenderBoxData, TextBoxData)
}

impl RenderBox {
    pure fn d(&self) -> &self/RenderBoxData {
        match *self {
            GenericBox(ref d)  => d,
            ImageBox(ref d, _) => d,
            TextBox(ref d, _)  => d
        }
    }
}

fn RenderBoxData(node: Node, ctx: @FlowContext, id: int) -> RenderBoxData {
    RenderBoxData {
        /* will be set if box is parented */
        tree : tree::empty(),
        node : node,
        mut ctx  : ctx,
        mut position : au::zero_rect(),
        font_size: Px(0.0),
        id : id
    }
}

impl RenderBox {
    pure fn is_replaced() -> bool {
        match self {
           ImageBox(*) => true, // TODO: form elements, etc
            _ => false
        }
    }

    /** In general, these functions are transitively impure because they
     * may cause glyphs to be allocated. For now, it's impure because of
     * holder.get_image()
    */
    fn get_min_width() -> au {
        match self {
            // TODO: this should account for min/pref widths of the
            // box element in isolation. That includes
            // border/margin/padding but not child widths. The block
            // FlowContext will combine the width of this element and
            // that of its children to arrive at the context width.
            GenericBox(*) => au(0),
            // TODO: consult CSS 'width', margin, border.
            // TODO: If image isn't available, consult 'width'.
            ImageBox(_,i) => au::from_px(i.get_size().get_default(Size2D(0,0)).width),
            TextBox(_,d) => d.runs.foldl(au(0), |sum, run| {
                au::max(sum, run.min_break_width())
            })
        }
    }

    fn get_pref_width() -> au {
        match self {
            // TODO: this should account for min/pref widths of the
            // box element in isolation. That includes
            // border/margin/padding but not child widths. The block
            // FlowContext will combine the width of this element and
            // that of its children to arrive at the context width.
            GenericBox(*) => au(0),
            ImageBox(_,i) => au::from_px(i.get_size().get_default(Size2D(0,0)).width),
            // TODO: account for line breaks, etc. The run should know
            // how to compute its own min and pref widths, and should
            // probably cache them.
            TextBox(_,d) => d.runs.foldl(au(0), |sum, run| {
                au::max(sum, run.size().width)
            })
        }
    }

    /* Returns the amount of left, right "fringe" used by this
    box. This should be based on margin, border, padding, width. */
    fn get_used_width() -> (au, au) {
        // TODO: this should actually do some computation!
        // See CSS 2.1, Section 10.3, 10.4.

        (au(0), au(0))
    }

    /* Returns the amount of left, right "fringe" used by this
    box. This should be based on margin, border, padding, width. */
    fn get_used_height() -> (au, au) {
        // TODO: this should actually do some computation!
        // See CSS 2.1, Section 10.5, 10.6.

        (au(0), au(0))
    }

    /* The box formed by the content edge, as defined in CSS 2.1 Section 8.1.
       Coordinates are relative to the owning flow. */
    pure fn content_box() -> Rect<au> {
        match self {
            ImageBox(_,i) => {
                let size = i.size();
                Rect {
                    origin: copy self.d().position.origin,
                    size:   Size2D(au::from_px(size.width),
                                   au::from_px(size.height))
                }
            },
            GenericBox(*) => {
                copy self.d().position
                /* FIXME: The following hits an ICE for whatever reason

                let origin = self.d().position.origin;
                let size   = self.d().position.size;
                let (offset_left, offset_right) = self.get_used_width();
                let (offset_top, offset_bottom) = self.get_used_height();

                Rect {
                    origin: Point2D(origin.x + offset_left, origin.y + offset_top),
                    size:   Size2D(size.width - (offset_left + offset_right),
                                   size.height - (offset_top + offset_bottom))
                }*/
            },
            TextBox(*) => {
                copy self.d().position
            }
        }
    }

    /* The box formed by the border edge, as defined in CSS 2.1 Section 8.1.
       Coordinates are relative to the owning flow. */
    fn border_box() -> Rect<au> {
        // TODO: actually compute content_box + padding + border
        self.content_box()
    }

    // TODO: to implement stacking contexts correctly, we need to
    // create a set of display lists, one per each layer of a stacking
    // context. (CSS 2.1, Section 9.9.1). Each box is passed the list
    // set representing the box's stacking context. When asked to
    // construct its constituent display items, each box puts its
    // DisplayItems into the correct stack layer (according to CSS 2.1
    // Appendix E).  and then builder flattens the list at the end.

    /* Methods for building a display list. This is a good candidate
       for a function pointer as the number of boxes explodes.

    # Arguments

    * `builder` - the display list builder which manages the coordinate system and options.
    * `dirty` - Dirty rectangle, in the coordinate system of the owning flow (self.ctx)
    * `origin` - Total offset from display list root flow to this box's owning flow
    * `list` - List to which items should be appended
    */
    fn build_display_list(_builder: &dl::DisplayListBuilder, dirty: &Rect<au>,
                          offset: &Point2D<au>, list: &dl::DisplayList) {
        if !self.d().position.intersects(dirty) {
            return;
        }

        let bounds : Rect<au> = Rect(self.d().position.origin.add(offset),
                                     copy self.d().position.size);

        match self {
            TextBox(_,d) => {
                let mut runs = d.runs;
                // TODO: don't paint background for text boxes
                list.push(~dl::SolidColor(bounds, 255u8, 255u8, 255u8));

                let mut bounds = bounds;
                for uint::range(0, runs.len()) |i| {
                    bounds.size.height = runs[i].size().height;
                    let glyph_run = make_glyph_run(&runs[i]);
                    list.push(~dl::Glyphs(bounds, glyph_run));
                    bounds.origin.y = bounds.origin.y.add(bounds.size.height);
                }
                return;

                pure fn make_glyph_run(text_run: &TextRun) -> dl::GlyphRun {
                    dl::GlyphRun {
                        glyphs: copy text_run.glyphs
                    }
                }
            },
            // TODO: items for background, border, outline
            GenericBox(*) => { },
            ImageBox(_,i) => {
                match i.get_image() {
                    Some(image) => list.push(~dl::Image(bounds, image)),
                    /* No image data at all? Okay, add some fallback content instead. */
                    None => {
                        // TODO: shouldn't need to unbox CSSValue by now
                        let boxed_color = self.d().node.style().background_color;
                        let color = match boxed_color {
                            Specified(BgColor(c)) => c,
                            Specified(BgColorTransparent) | _ => util::color::rgba(0,0,0,0.0)
                        };
                        list.push(~dl::SolidColor(bounds, color.red, color.green, color.blue));
                    }
                }
            }
        }
    }
}

trait ImageBoxMethods {

}

/**
 * The tree holding render box relations. These are only defined for
 * nested CSS boxes that are nested in an otherwise inline flow
 * context.
*/
enum RenderBoxTree { RenderBoxTree }

impl RenderBoxTree : tree::ReadMethods<@RenderBox> {
    fn each_child(node: @RenderBox, f: fn(&&@RenderBox) -> bool) {
        tree::each_child(self, node, f)
    }

    fn with_tree_fields<R>(&&b: @RenderBox, f: fn(tree::Tree<@RenderBox>) -> R) -> R {
        f(b.d().tree)
    }
}

impl RenderBoxTree : tree::WriteMethods<@RenderBox> {
    fn add_child(parent: @RenderBox, child: @RenderBox) {
        assert !core::box::ptr_eq(parent, child);
        tree::add_child(self, parent, child)
    }

    fn with_tree_fields<R>(&&b: @RenderBox, f: fn(tree::Tree<@RenderBox>) -> R) -> R {
        f(b.d().tree)
    }
}

impl RenderBox : BoxedDebugMethods {
    fn dump(@self) {
        self.dump_indent(0u);
    }

    /* Dumps the node tree, for debugging, with indentation. */
    fn dump_indent(@self, indent: uint) {
        let mut s = ~"";
        for uint::range(0u, indent) |_i| {
            s += ~"    ";
        }

        s += self.debug_str();
        debug!("%s", s);

        for RenderBoxTree.each_child(self) |kid| {
            kid.dump_indent(indent + 1u)
        }
    }

    fn debug_str(@self) -> ~str {
        let repr = match self {
            @GenericBox(*) => ~"GenericBox",
            @ImageBox(*) => ~"ImageBox",
            @TextBox(_,d) => {
                let mut s = d.runs.foldl(~"TextBox(runs=", |s, run| {
                    fmt!("%s  \"%s\"", s, run.text)
                });
                s += ~")"; s
            }
        };

        fmt!("box b%?: %?", self.d().id, repr)
    }
}

#[cfg(test)]
mod test {
    use dom::element::{ElementData, HTMLDivElement, HTMLImageElement};
    use dom::node::{Element, NodeScope, Node, NodeKind};
    use dom::rcu::Scope;

    /*
    use sdl;
    use sdl::video;

    fn with_screen(f: fn(*sdl::surface)) {
        let screen = video::set_video_mode(
            320, 200, 32,
            ~[video::hwsurface], ~[video::doublebuf]);
        assert screen != ptr::null();

        f(screen);

        video::free_surface(screen);
    }
    */

    fn flat_bounds(root: @RenderBox) -> ~[Rect<au>] {
        let mut r = ~[];
        for tree::each_child(RenderBoxTree, root) |c| {
            push_all(r, flat_bounds(c));
        }

        push(r, copy root.data.position);

        return r;
    }

    // TODO: redo tests here, but probably is part of box_builder.rs
}

<commit_msg>Fix a test.<commit_after>/* Fundamental layout structures and algorithms. */

use arc = std::arc;
use arc::ARC;
use au = gfx::geometry;
use au::au;
use core::dvec::DVec;
use core::to_str::ToStr;
use core::rand;
use css::styles::SpecifiedStyle;
use css::values::{BoxSizing, Length, Px, CSSDisplay, Specified, BgColor, BgColorTransparent};
use dl = gfx::display_list;
use dom::element::{ElementKind, HTMLDivElement, HTMLImageElement};
use dom::node::{Element, Node, NodeData, NodeKind, NodeTree};
use dom::rcu;
use geom::rect::Rect;
use geom::size::Size2D;
use geom::point::Point2D;
use image::{Image, ImageHolder};
use layout::context::LayoutContext;
use layout::debug::BoxedDebugMethods;
use layout::flow::FlowContext;
use layout::text::TextBoxData;
use servo_text::text_run::TextRun;
use std::net::url::Url;
use task::spawn;
use util::color::Color;
use util::tree;
use vec::{push, push_all};

/**
Render boxes (`struct RenderBox`) are the leafs of the layout
tree. They cannot position themselves. In general, render boxes do not
have a simple correspondence with CSS boxes as in the specification:

 * Several render boxes may correspond to the same CSS box or DOM
   node. For example, a CSS text box broken across two lines is
   represented by two render boxes.

 * Some CSS boxes are not created at all, such as some anonymous block
   boxes induced by inline boxes with block-level sibling boxes. In
   that case, Servo uses an InlineFlow with BlockFlow siblings; the
   InlineFlow is block-level, but not a block container. It is
   positioned as if it were a block box, but its children are
   positioned according to inline flow.

Fundamental box types include:

 * GenericBox: an empty box that contributes only borders, margins,
padding, backgrounds. It is analogous to a CSS nonreplaced content box.

 * ImageBox: a box that represents a (replaced content) image and its
   accompanying borders, shadows, etc.

 * TextBox: a box representing a single run of text with a distinct
   style. A TextBox may be split into two or more render boxes across
   line breaks. Several TextBoxes may correspond to a single DOM text
   node. Split text boxes are implemented by referring to subsets of a
   master TextRun object.

*/

/* A box's kind influences how its styles are interpreted during
   layout.  For example, replaced content such as images are resized
   differently than tables, text, or other content.

   It also holds data specific to different box types, such as text.
*/

struct RenderBoxData {
    /* references to children, parent inline flow boxes  */
    tree : tree::Tree<@RenderBox>,
    /* originating DOM node */
    node : Node,
    /* reference to containing flow context, which this box
       participates in */
    ctx  : @FlowContext,
    /* position of this box relative to owning flow */
    mut position : Rect<au>,
    font_size : Length,
    /* TODO (Issue #87): debug only */
    mut id: int
}

enum RenderBoxType {
    RenderBox_Generic,
    RenderBox_Image,
    RenderBox_Text
}

enum RenderBox {
    GenericBox(RenderBoxData),
    ImageBox(RenderBoxData, ImageHolder),
    TextBox(RenderBoxData, TextBoxData)
}

impl RenderBox {
    pure fn d(&self) -> &self/RenderBoxData {
        match *self {
            GenericBox(ref d)  => d,
            ImageBox(ref d, _) => d,
            TextBox(ref d, _)  => d
        }
    }
}

fn RenderBoxData(node: Node, ctx: @FlowContext, id: int) -> RenderBoxData {
    RenderBoxData {
        /* will be set if box is parented */
        tree : tree::empty(),
        node : node,
        mut ctx  : ctx,
        mut position : au::zero_rect(),
        font_size: Px(0.0),
        id : id
    }
}

impl RenderBox {
    pure fn is_replaced() -> bool {
        match self {
           ImageBox(*) => true, // TODO: form elements, etc
            _ => false
        }
    }

    /** In general, these functions are transitively impure because they
     * may cause glyphs to be allocated. For now, it's impure because of
     * holder.get_image()
    */
    fn get_min_width() -> au {
        match self {
            // TODO: this should account for min/pref widths of the
            // box element in isolation. That includes
            // border/margin/padding but not child widths. The block
            // FlowContext will combine the width of this element and
            // that of its children to arrive at the context width.
            GenericBox(*) => au(0),
            // TODO: consult CSS 'width', margin, border.
            // TODO: If image isn't available, consult 'width'.
            ImageBox(_,i) => au::from_px(i.get_size().get_default(Size2D(0,0)).width),
            TextBox(_,d) => d.runs.foldl(au(0), |sum, run| {
                au::max(sum, run.min_break_width())
            })
        }
    }

    fn get_pref_width() -> au {
        match self {
            // TODO: this should account for min/pref widths of the
            // box element in isolation. That includes
            // border/margin/padding but not child widths. The block
            // FlowContext will combine the width of this element and
            // that of its children to arrive at the context width.
            GenericBox(*) => au(0),
            ImageBox(_,i) => au::from_px(i.get_size().get_default(Size2D(0,0)).width),
            // TODO: account for line breaks, etc. The run should know
            // how to compute its own min and pref widths, and should
            // probably cache them.
            TextBox(_,d) => d.runs.foldl(au(0), |sum, run| {
                au::max(sum, run.size().width)
            })
        }
    }

    /* Returns the amount of left, right "fringe" used by this
    box. This should be based on margin, border, padding, width. */
    fn get_used_width() -> (au, au) {
        // TODO: this should actually do some computation!
        // See CSS 2.1, Section 10.3, 10.4.

        (au(0), au(0))
    }

    /* Returns the amount of left, right "fringe" used by this
    box. This should be based on margin, border, padding, width. */
    fn get_used_height() -> (au, au) {
        // TODO: this should actually do some computation!
        // See CSS 2.1, Section 10.5, 10.6.

        (au(0), au(0))
    }

    /* The box formed by the content edge, as defined in CSS 2.1 Section 8.1.
       Coordinates are relative to the owning flow. */
    pure fn content_box() -> Rect<au> {
        match self {
            ImageBox(_,i) => {
                let size = i.size();
                Rect {
                    origin: copy self.d().position.origin,
                    size:   Size2D(au::from_px(size.width),
                                   au::from_px(size.height))
                }
            },
            GenericBox(*) => {
                copy self.d().position
                /* FIXME: The following hits an ICE for whatever reason

                let origin = self.d().position.origin;
                let size   = self.d().position.size;
                let (offset_left, offset_right) = self.get_used_width();
                let (offset_top, offset_bottom) = self.get_used_height();

                Rect {
                    origin: Point2D(origin.x + offset_left, origin.y + offset_top),
                    size:   Size2D(size.width - (offset_left + offset_right),
                                   size.height - (offset_top + offset_bottom))
                }*/
            },
            TextBox(*) => {
                copy self.d().position
            }
        }
    }

    /* The box formed by the border edge, as defined in CSS 2.1 Section 8.1.
       Coordinates are relative to the owning flow. */
    fn border_box() -> Rect<au> {
        // TODO: actually compute content_box + padding + border
        self.content_box()
    }

    // TODO: to implement stacking contexts correctly, we need to
    // create a set of display lists, one per each layer of a stacking
    // context. (CSS 2.1, Section 9.9.1). Each box is passed the list
    // set representing the box's stacking context. When asked to
    // construct its constituent display items, each box puts its
    // DisplayItems into the correct stack layer (according to CSS 2.1
    // Appendix E).  and then builder flattens the list at the end.

    /* Methods for building a display list. This is a good candidate
       for a function pointer as the number of boxes explodes.

    # Arguments

    * `builder` - the display list builder which manages the coordinate system and options.
    * `dirty` - Dirty rectangle, in the coordinate system of the owning flow (self.ctx)
    * `origin` - Total offset from display list root flow to this box's owning flow
    * `list` - List to which items should be appended
    */
    fn build_display_list(_builder: &dl::DisplayListBuilder, dirty: &Rect<au>,
                          offset: &Point2D<au>, list: &dl::DisplayList) {
        if !self.d().position.intersects(dirty) {
            return;
        }

        let bounds : Rect<au> = Rect(self.d().position.origin.add(offset),
                                     copy self.d().position.size);

        match self {
            TextBox(_,d) => {
                let mut runs = d.runs;
                // TODO: don't paint background for text boxes
                list.push(~dl::SolidColor(bounds, 255u8, 255u8, 255u8));

                let mut bounds = bounds;
                for uint::range(0, runs.len()) |i| {
                    bounds.size.height = runs[i].size().height;
                    let glyph_run = make_glyph_run(&runs[i]);
                    list.push(~dl::Glyphs(bounds, glyph_run));
                    bounds.origin.y = bounds.origin.y.add(bounds.size.height);
                }
                return;

                pure fn make_glyph_run(text_run: &TextRun) -> dl::GlyphRun {
                    dl::GlyphRun {
                        glyphs: copy text_run.glyphs
                    }
                }
            },
            // TODO: items for background, border, outline
            GenericBox(*) => { },
            ImageBox(_,i) => {
                match i.get_image() {
                    Some(image) => list.push(~dl::Image(bounds, image)),
                    /* No image data at all? Okay, add some fallback content instead. */
                    None => {
                        // TODO: shouldn't need to unbox CSSValue by now
                        let boxed_color = self.d().node.style().background_color;
                        let color = match boxed_color {
                            Specified(BgColor(c)) => c,
                            Specified(BgColorTransparent) | _ => util::color::rgba(0,0,0,0.0)
                        };
                        list.push(~dl::SolidColor(bounds, color.red, color.green, color.blue));
                    }
                }
            }
        }
    }
}

trait ImageBoxMethods {

}

/**
 * The tree holding render box relations. These are only defined for
 * nested CSS boxes that are nested in an otherwise inline flow
 * context.
*/
enum RenderBoxTree { RenderBoxTree }

impl RenderBoxTree : tree::ReadMethods<@RenderBox> {
    fn each_child(node: @RenderBox, f: fn(&&@RenderBox) -> bool) {
        tree::each_child(self, node, f)
    }

    fn with_tree_fields<R>(&&b: @RenderBox, f: fn(tree::Tree<@RenderBox>) -> R) -> R {
        f(b.d().tree)
    }
}

impl RenderBoxTree : tree::WriteMethods<@RenderBox> {
    fn add_child(parent: @RenderBox, child: @RenderBox) {
        assert !core::box::ptr_eq(parent, child);
        tree::add_child(self, parent, child)
    }

    fn with_tree_fields<R>(&&b: @RenderBox, f: fn(tree::Tree<@RenderBox>) -> R) -> R {
        f(b.d().tree)
    }
}

impl RenderBox : BoxedDebugMethods {
    fn dump(@self) {
        self.dump_indent(0u);
    }

    /* Dumps the node tree, for debugging, with indentation. */
    fn dump_indent(@self, indent: uint) {
        let mut s = ~"";
        for uint::range(0u, indent) |_i| {
            s += ~"    ";
        }

        s += self.debug_str();
        debug!("%s", s);

        for RenderBoxTree.each_child(self) |kid| {
            kid.dump_indent(indent + 1u)
        }
    }

    fn debug_str(@self) -> ~str {
        let repr = match self {
            @GenericBox(*) => ~"GenericBox",
            @ImageBox(*) => ~"ImageBox",
            @TextBox(_,d) => {
                let mut s = d.runs.foldl(~"TextBox(runs=", |s, run| {
                    fmt!("%s  \"%s\"", s, run.text)
                });
                s += ~")"; s
            }
        };

        fmt!("box b%?: %?", self.d().id, repr)
    }
}

#[cfg(test)]
mod test {
    use dom::element::{ElementData, HTMLDivElement, HTMLImageElement};
    use dom::node::{Element, NodeScope, Node, NodeKind};
    use dom::rcu::Scope;

    /*
    use sdl;
    use sdl::video;

    fn with_screen(f: fn(*sdl::surface)) {
        let screen = video::set_video_mode(
            320, 200, 32,
            ~[video::hwsurface], ~[video::doublebuf]);
        assert screen != ptr::null();

        f(screen);

        video::free_surface(screen);
    }
    */

    fn flat_bounds(root: @RenderBox) -> ~[Rect<au>] {
        let mut r = ~[];
        for tree::each_child(RenderBoxTree, root) |c| {
            push_all(r, flat_bounds(c));
        }

        push(r, copy root.d().position);

        return r;
    }

    // TODO: redo tests here, but probably is part of box_builder.rs
}

<|endoftext|>


<commit_before>use std::fmt::Debug;
use std::default::Default;
use rustbox::RustBox;
use rustbox::keyboard::Key;

use rex_utils;
use rex_utils::rect::Rect;

use super::common::Canceled;
use super::input::Input;
use super::widget::Widget;
use super::RustBoxEx::{RustBoxEx, Style};

pub enum MenuActions {
    Key(char),
    Back,
    Cancel,
}

pub enum MenuEntry<'a, T> where T: 'a {
    SubEntries(char, &'a str, &'a [MenuEntry<'a, T>]),
    CommandEntry(char, &'a str, T)
}

pub type MenuState<T> = &'static [MenuEntry<'static, T>];

pub struct OverlayMenu<T> where T: 'static {
    root_menu: MenuState<T>,
    menu_stack: Vec<MenuState<T>>,
    pub on_cancel: Canceled,
}

impl<T> OverlayMenu<T> {
    pub fn with_menu(root_menu: MenuState<T>) -> OverlayMenu<T> {
        OverlayMenu {
            root_menu: root_menu,
            menu_stack: vec![],
            on_cancel: Default::default()
        }
    }

    fn menu_act_key(&mut self, c: char) -> bool {
        for entry in self.current_menu().iter() {
            match entry {
                &MenuEntry::CommandEntry(key, _, _) if key == c => {
                    println!("Found key = {}", key);
                    return true;
                }
                &MenuEntry::SubEntries(key, _, sub_menu) if key == c => {
                    self.menu_stack.push(sub_menu);
                    return true;
                }
                _ => ()
            }
        }
        return false;
    }

    fn current_menu(&self) -> MenuState<T> {
        self.menu_stack.last().unwrap_or(&self.root_menu)
    }

    fn menu_back(&mut self) {
        self.menu_stack.pop();
    }
}

impl<T> Widget for OverlayMenu<T> {
    fn input(&mut self, input: &Input, key: Key) -> bool {
        let action = if let Some(action) = input.menu_input(key) { action } else {
            return false;
        };

        match action {
            MenuActions::Back => { self.menu_back(); true}
            MenuActions::Key(c) => self.menu_act_key(c),
            MenuActions::Cancel => {
                self.on_cancel.signal(None);
                true
            }
        }
    }

    fn draw(&mut self, rb: &RustBox, area: Rect<isize>, has_focus: bool) {
        let clear_line = rex_utils::string_with_repeat(' ', area.width as usize);
        for i in 0..(area.height as usize) {
            rb.print_style(area.left as usize, area.top as usize + i, Style::Default, &clear_line);
        }

        for (i, entry) in self.current_menu().iter().enumerate() {
            let (key, name, is_title, style) = match entry {
                &MenuEntry::CommandEntry(key, name, _) => (key, name, false, Style::MenuEntry),
                &MenuEntry::SubEntries(key, name, _) => (key, name, true, Style::MenuTitle),
            };
            rb.print_slice_style(10 + area.left as usize, area.top as usize + i, Style::MenuShortcut, &[key, ' ']);
            rb.print_style(10 + area.left as usize + 2, area.top as usize + i, style, name);
            if is_title {
                rb.print_style(10 + area.left as usize + 2 + name.len() + 1, area.top as usize + i, style, "->");
            }
        }

        rb.set_cursor(-1, -1);
    }
}
<commit_msg>Change the overlay menu to not be generic over the menu action type for now The signals implementation does not supports generics for now, so this makes signal usage with the overlay menu problematic.<commit_after>use std::fmt::Debug;
use std::default::Default;
use rustbox::RustBox;
use rustbox::keyboard::Key;

use rex_utils;
use rex_utils::rect::Rect;

use super::common::Canceled;
use super::input::Input;
use super::widget::Widget;
use super::view::HexEditActions;
use super::RustBoxEx::{RustBoxEx, Style};

pub enum MenuActions {
    Key(char),
    Back,
    Cancel,
}

pub enum MenuEntry<'a, T> where T: 'a {
    SubEntries(char, &'a str, &'a [MenuEntry<'a, T>]),
    CommandEntry(char, &'a str, T)
}

pub type MenuState<T> = &'static [MenuEntry<'static, T>];

pub struct OverlayMenu {
    root_menu: MenuState<HexEditActions>,
    menu_stack: Vec<MenuState<HexEditActions>>,
    pub on_cancel: Canceled,
}

impl OverlayMenu {
    pub fn with_menu(root_menu: MenuState<HexEditActions>) -> OverlayMenu {
        OverlayMenu {
            root_menu: root_menu,
            menu_stack: vec![],
            on_cancel: Default::default()
        }
    }

    fn menu_act_key(&mut self, c: char) -> bool {
        for entry in self.current_menu().iter() {
            match entry {
                &MenuEntry::CommandEntry(key, _, _) if key == c => {
                    println!("Found key = {}", key);
                    return true;
                }
                &MenuEntry::SubEntries(key, _, sub_menu) if key == c => {
                    self.menu_stack.push(sub_menu);
                    return true;
                }
                _ => ()
            }
        }
        return false;
    }

    fn current_menu(&self) -> MenuState<HexEditActions> {
        self.menu_stack.last().unwrap_or(&self.root_menu)
    }

    fn menu_back(&mut self) {
        self.menu_stack.pop();
    }
}

impl Widget for OverlayMenu {
    fn input(&mut self, input: &Input, key: Key) -> bool {
        let action = if let Some(action) = input.menu_input(key) { action } else {
            return false;
        };

        match action {
            MenuActions::Back => { self.menu_back(); true}
            MenuActions::Key(c) => self.menu_act_key(c),
            MenuActions::Cancel => {
                self.on_cancel.signal(None);
                true
            }
        }
    }

    fn draw(&mut self, rb: &RustBox, area: Rect<isize>, has_focus: bool) {
        let clear_line = rex_utils::string_with_repeat(' ', area.width as usize);
        for i in 0..(area.height as usize) {
            rb.print_style(area.left as usize, area.top as usize + i, Style::Default, &clear_line);
        }

        for (i, entry) in self.current_menu().iter().enumerate() {
            let (key, name, is_title, style) = match entry {
                &MenuEntry::CommandEntry(key, name, _) => (key, name, false, Style::MenuEntry),
                &MenuEntry::SubEntries(key, name, _) => (key, name, true, Style::MenuTitle),
            };
            rb.print_slice_style(10 + area.left as usize, area.top as usize + i, Style::MenuShortcut, &[key, ' ']);
            rb.print_style(10 + area.left as usize + 2, area.top as usize + i, style, name);
            if is_title {
                rb.print_style(10 + area.left as usize + 2 + name.len() + 1, area.top as usize + i, style, "->");
            }
        }

        rb.set_cursor(-1, -1);
    }
}
<|endoftext|>


<commit_before>//! M17n
//!
//! The docs in this module quote the [m17n "Data format of the m17n database"
//! docs][1] a lot.
//!
//! [1]: https://www.nongnu.org/m17n/manual-en/m17nDBFormat.html

use regex::Regex;
use snafu::ResultExt;
use std::{convert::TryFrom, str::FromStr};

mod ser;
pub use ser::ToMim;

/// M17n Input Method, i.e., the content of a `.mim` file
//
// INPUT-METHOD ::=
//     IM-DECLARATION ? IM-DESCRIPTION ? TITLE ?
//      VARIABLE-LIST ? COMMAND-LIST ?  MODULE-LIST ?
//      MACRO-LIST ? MAP-LIST ? STATE-LIST ?
//
//
// IM-DESCRIPTION ::= '(' 'description' DESCRIPTION ')'
// DESCRIPTION ::= MTEXT-OR-GETTEXT | 'nil'
// MTEXT-OR-GETTEXT ::=  [ MTEXT | '(' '_' MTEXT ')']
//
// TITLE ::= '(' 'title' TITLE-TEXT ')'
// TITLE-TEXT ::= MTEXT
pub struct Root {
    pub input_method: InputMethod,
    pub description: Option<Text>,
    pub title: Text,
    // variable_list: Vec<Variable>,
    // command_list: Vec<Command>,
    // module_list: Vec<Mgodule>,
    // macro_list: Vec<Macro>,
    pub maps: Vec<Map>,
    pub states: Vec<State>,
}

// IM-DECLARATION ::= '(' 'input-method' LANGUAGE NAME EXTRA-ID ? VERSION ? ')'
// LANGUAGE ::= SYMBOL
// NAME ::= SYMBOL
// EXTRA-ID ::= SYMBOL
// VERSION ::= '(' 'version' VERSION-NUMBER ')'
pub struct InputMethod {
    pub language: Symbol,
    pub name: Symbol,
    pub extra_id: Option<Symbol>,
    pub version: Option<String>,
}

// MAP-LIST ::= MAP-INCLUSION ? '(' 'map' MAP * ')'
// MAP ::= '(' MAP-NAME RULE * ')'
// MAP-NAME ::= SYMBOL
pub struct Map {
    pub name: Symbol,
    pub rules: Vec<Rule>,
}

// RULE ::= '(' KEYSEQ MAP-ACTION * ')'
pub struct Rule {
    /// Keys to press to produce mapping
    pub keyseq: KeySeq,
    /// The mapping describes what to do
    ///
    /// Right now we only support inserting characters.
    pub action: MapAction,
}

// KEYSEQ ::= MTEXT | '(' [ SYMBOL | INTEGER ] * ')'
pub enum KeySeq {
    /// > MTEXT in the definition of `KEYSEQ` consists of characters that can be
    /// > generated by a keyboard. Therefore MTEXT usually contains only ASCII
    /// > characters. However, if the input method is intended to be used, for
    /// > instance, with a West European keyboard, MTEXT may contain Latin-1
    /// > characters.
    Character(Text),
    /// > If the shift, control, meta, alt, super, and hyper modifiers are used,
    /// > they are represented by the `S-`, `C-`, `M-`, `A-`, `s-`, and `H-`
    /// > prefixes respectively in this order. Thus, "return with shift with
    /// > meta with hyper" is `(S-M-H-Return)`. Note that "a with shift" .. "z
    /// > with shift" are represented simply as `A .. Z`. Thus "a with shift
    /// > with meta with hyper" is `(M-H-A)`.
    KeyCombo(KeyCombo),
}

pub struct KeyCombo {
    /// Represents something like `C-S` for "Control + Shift"
    pub modifiers: Vec<Modifier>,
    // Can be TEXT or INTEGER but let's be conservative and expect a character
    // code.
    pub key: KeyDef,
}

pub enum KeyDef {
    /// > INTEGER in the definition of `KEYSEQ` must be a valid character code.
    CharacterCode(Integer),
    /// > SYMBOL in the definition of `KEYSEQ` must be the return value of the
    /// > `minput_event_to_key()` function. Under the X window system, you can
    /// > quickly check the value using the `xev` command. For example, the
    /// > return key, the backspace key, and the 0 key on the keypad are
    /// > represented as `(Return)`, `(BackSpace)`, and `(KP_0)` respectively.
    Character(Symbol),
}

/// Modifier keys
///
/// > "S-" (Shift), "C-" (Control), "M-" (Meta), "A-" (Alt), "G-" (AltGr), "s-"
/// > (Super), and "H-" (Hyper)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(strum_macros::EnumString, strum_macros::Display)]
#[strum(serialize_all = "snake_case")]
pub enum Modifier {
    Shift,
    #[strum(serialize = "ctrl", serialize = "control")]
    Control,
    /// Often one of the Alt keys in terminals
    Meta,
    Alt,
    AltGr,
    /// Super key is assumed to be Command key on macOS
    #[strum(serialize = "super", serialize = "cmd")]
    Super,
    /// Mo layers, mo fun. Let's pick caps lock for this.
    #[strum(serialize = "hyper", serialize = "caps")]
    Hyper,
}

impl Modifier {
    /// Parse kbdgen-typical key combo syntax
    ///
    /// ```rust
    /// use kbdgen::m17n_mim::Modifier;
    ///
    /// assert_eq!(
    ///     Modifier::parse_keycombo("ctrl").unwrap(),
    ///     vec![Modifier::Control],
    /// );
    /// assert_eq!(
    ///     Modifier::parse_keycombo("alt+shift").unwrap(),
    ///     vec![Modifier::Alt, Modifier::Shift],
    /// );
    /// assert_eq!(
    ///     Modifier::parse_keycombo("cmd+alt").unwrap(),
    ///     vec![Modifier::Super, Modifier::Alt],
    /// );
    /// ```
    pub fn parse_keycombo(input: &str) -> Result<Vec<Modifier>, MimConversion> {
        let mut res = vec![];
        for m in input.split('+') {
            if let Ok(m) = Modifier::from_str(m) {
                res.push(m);
            } else {
                InvalidKeyCombo {
                    input: format!("unknown modifier `{}`", m),
                }
                .fail()?;
            }
        }
        Ok(res)
    }
}

// Originally a quite complex type, defined as:
//
// ```bnf
// MAP-ACTION ::= ACTION
//
// ACTION ::= INSERT | DELETE | SELECT | MOVE | MARK
//            | SHOW | HIDE | PUSHBACK | POP | UNDO
// 	          | COMMIT | UNHANDLE | SHIFT | CALL
// 	          | SET | IF | COND | '(' MACRO-NAME ')'
//
// PREDEFINED-SYMBOL ::=
//     '@0' | '@1' | '@2' | '@3' | '@4'
//     | '@5' | '@6' | '@7' | '@8' | '@9'
//     | '@<' | '@=' | '@>' | '@-' | '@+' | '@[' | '@]'
//     | '@@'
//     | '@-0' | '@-N' | '@+N'
// ```
//
// but we've only implemented the trivial Insert variants at the moment.
pub enum MapAction {
    Insert(Insert),

    #[cfg(sorry_not_yet_implemented)]
    Delete(Delete),
    #[cfg(sorry_not_yet_implemented)]
    Select(Select),
    #[cfg(sorry_not_yet_implemented)]
    Move(Move),
    #[cfg(sorry_not_yet_implemented)]
    Mark(Mark),
    #[cfg(sorry_not_yet_implemented)]
    Show(Show),
    #[cfg(sorry_not_yet_implemented)]
    Hide(Hide),
    #[cfg(sorry_not_yet_implemented)]
    Pushback(Pushback),
    #[cfg(sorry_not_yet_implemented)]
    Pop(Pop),
    #[cfg(sorry_not_yet_implemented)]
    Undo(Undo),
    #[cfg(sorry_not_yet_implemented)]
    Commit(Commit),
    #[cfg(sorry_not_yet_implemented)]
    Unhandle(Unhandle),
    #[cfg(sorry_not_yet_implemented)]
    Shift(Shift),
    #[cfg(sorry_not_yet_implemented)]
    Call(Call),
    #[cfg(sorry_not_yet_implemented)]
    Set(Set),
    #[cfg(sorry_not_yet_implemented)]
    If(If),
    #[cfg(sorry_not_yet_implemented)]
    Cond(Cond),
    #[cfg(sorry_not_yet_implemented)]
    Macro(Symbol),
}

/// Insert action: insert something before the current position
//
// Originally defined as:
//
// ```bnf
// INSERT ::= '(' 'insert' MTEXT ')'
//             | MTEXT
//             | INTEGER
//             | SYMBOL
//             | '(' 'insert' SYMBOL ')'
//             | '(' 'insert' '(' CANDIDATES * ')' ')'
//             | '(' CANDIDATES * ')'
//
// CANDIDATES ::= MTEXT | '(' MTEXT * ')'
// ```
//
// but we'll only use the simple forms for now
pub enum Insert {
    /// > insert TEXT before the current position
    Character(Text),
    /// > inserts the character INTEGER before the current position
    CharacterCode(Integer),

    #[cfg(sorry_not_yet_implemented)]
    /// > treats SYMBOL as a variable, and inserts its value (if it is a valid
    /// > character code) before the current position
    KeyCombo(Symbol),

    #[cfg(sorry_not_yet_implemented)]
    /// > each CANDIDATES represents a candidate group, and each element of
    /// >CANDIDATES represents a candidate, i.e. if CANDIDATES is an M-text, the
    /// >candidates are the characters in the M-text; if CANDIDATES is a list of
    /// >M-texts, the candidates are the M-texts in the list.
    /// >
    /// >These forms insert the first candidate before the current position. The
    /// >inserted string is associated with the list of candidates and the
    /// >information indicating the currently selected candidate.
    Candidates(Vec<Text>),
}

// STATE-LIST ::= STATE-INCUSION ? '(' 'state' STATE * ')'  STATE-INCUSION ?
// STATE ::= '(' STATE-NAME [ STATE-TITLE-TEXT ] BRANCH * ')'
// STATE-NAME ::= SYMBOL
// STATE-TITLE-TEXT ::= MTEXT
// STATE-INCLUSION ::= '(' 'include' TAGS 'state' STATE-NAME ? ')'
pub struct State {
    pub name: Symbol,
    pub title: Option<Text>,
    pub branches: Vec<Branch>,
}

// BRANCH ::= '(' MAP-NAME BRANCH-ACTION * ')'
// 	   | '(' 'nil' BRANCH-ACTION * ')'
// 	   | '(' 't' BRANCH-ACTION * ')'
pub struct Branch {
    pub map_name: Symbol,

    #[cfg(sorry_not_yet_implemented)]
    pub actions: Vec<Action>,
}

/// The "MSymbol" type
///
/// [Defined](https://www.nongnu.org/m17n/manual-en/m17nDBFormat.html) as:
///
/// > An element that matches the regular expression `[^-0-9(]([^\()]|\.)+`
/// > represents a property whose key is `Msymbol`. In the element, `\t`, `\n`,
/// > `\r`, and `\e` are replaced with tab (code 9), newline (code 10), carriage
/// > return (code 13), and escape (code 27) respectively. Other characters
/// > following a backslash is interpreted as it is. The value of the property
/// > is the symbol having the resulting string as its name.
/// >
/// > For instance, the element `abc\ def` represents a property whose value is
/// > the symbol having the name "abc def".
#[derive(Clone, Debug)]
pub struct Symbol(String);

impl TryFrom<String> for Symbol {
    type Error = MimConversion;

    fn try_from(x: String) -> Result<Self, Self::Error> {
        // FIXME: Escaping and stuff
        Ok(Symbol(x))
    }
}

/// The "MText" type
///
/// [Defined](https://www.nongnu.org/m17n/manual-en/m17nDBFormat.html) as:
///
/// > An element that matches the regular expression `([^"]|\")*` represents a
/// > property whose key is `Mtext`. The backslash escape explained above also
/// > applies here. Moreover, each part in the element matching the regular
/// > expression `\[xX][0-9A-Fa-f][0-9A-Fa-f]` is replaced with its hexadecimal
/// > interpretation.
///
/// > After having resolved the backslash escapes, the byte sequence between the
/// > double quotes is interpreted as a UTF-8 sequence and decoded into an
/// > M-text. This M-text is the value of the property.
#[derive(Clone, Debug)]
pub struct Text(String);

impl TryFrom<String> for Text {
    type Error = MimConversion;

    fn try_from(input: String) -> Result<Self, Self::Error> {
        // FIXME: Escaping and stuff
        // FIXME: transforming existing escapes like `\u{30A}` to `\x30A`

        lazy_static::lazy_static! {
            static ref RE: Regex = Regex::new(r"\\u\{([0-9A-Fa-f]{1,6})\}").expect("valid regex");
        }

        let new = RE.replace_all(&input, |cap: &regex::Captures| {
            let hex = cap.get(1).unwrap().as_str();
            format!("\\x{}", hex)
        });

        Ok(Text(new.to_string()))
    }
}

/// The "Minteger" type
///
/// [Defined](https://www.nongnu.org/m17n/manual-en/m17nDBFormat.html) as:
///
/// > An element that matches the regular expression `-?[0-9]+ or
/// > 0[xX][0-9A-Fa-f]+` represents a property whose key is `Minteger`. An
/// > element matching the former expression is interpreted as an integer in
/// > decimal notation, and one matching the latter is interpreted as an integer
/// > in hexadecimal notation. The value of the property is the result of
/// > interpretation.
/// >
/// > For instance, the element `0xA0` represents a property whose value is 160
/// > in decimal.
#[derive(Clone, Debug)]
pub struct Integer(String);

impl TryFrom<String> for Integer {
    type Error = MimConversion;

    fn try_from(input: String) -> Result<Self, Self::Error> {
        // decimal
        if u32::from_str_radix(&input, 10).is_ok() {
            return Ok(Integer(input));
        }

        // hex
        if !(input.starts_with("0x") || input.starts_with("0X")) {
            return InvalidIntegerHexPrefix { input }.fail();
        }
        let _parsed = u64::from_str_radix(&input[2..], 16)
            .with_context(|| InvalidIntegerHexValue { input: &input })?;
        Ok(Integer(input))
    }
}

/// Possible errors when converting values to their MIM representation
#[derive(snafu::Snafu, Debug)]
pub enum MimConversion {
    #[snafu(display("Could serialize MIM symbol"))]
    InvalidSymbol { backtrace: snafu::Backtrace },
    #[snafu(display("Could serialize MIM text"))]
    InvalidText { backtrace: snafu::Backtrace },
    #[snafu(display("Could serialize MIM key combo: {}", input))]
    InvalidKeyCombo {
        input: String,
        backtrace: snafu::Backtrace,
    },
    #[snafu(display("Could not map index `{}` to a character code", index))]
    InvalidCharactorCodeIndex {
        index: usize,
        backtrace: snafu::Backtrace,
    },
    #[snafu(display(
        "Assumed hexadecimal MIM integer but there was no `0x` prefix in `{}`",
        input
    ))]
    InvalidIntegerHexPrefix {
        input: String,
        backtrace: snafu::Backtrace,
    },
    #[snafu(display(
        "Assumed hexadecimal MIM integer but `{}` is not a valid hex value",
        input
    ))]
    InvalidIntegerHexValue {
        input: String,
        source: std::num::ParseIntError,
        backtrace: snafu::Backtrace,
    },
}
<commit_msg>Make it compile on 1.35 again<commit_after>//! M17n
//!
//! The docs in this module quote the [m17n "Data format of the m17n database"
//! docs][1] a lot.
//!
//! [1]: https://www.nongnu.org/m17n/manual-en/m17nDBFormat.html

use regex::Regex;
use snafu::IntoError;
use std::{convert::TryFrom, str::FromStr};

mod ser;
pub use ser::ToMim;

/// M17n Input Method, i.e., the content of a `.mim` file
//
// INPUT-METHOD ::=
//     IM-DECLARATION ? IM-DESCRIPTION ? TITLE ?
//      VARIABLE-LIST ? COMMAND-LIST ?  MODULE-LIST ?
//      MACRO-LIST ? MAP-LIST ? STATE-LIST ?
//
//
// IM-DESCRIPTION ::= '(' 'description' DESCRIPTION ')'
// DESCRIPTION ::= MTEXT-OR-GETTEXT | 'nil'
// MTEXT-OR-GETTEXT ::=  [ MTEXT | '(' '_' MTEXT ')']
//
// TITLE ::= '(' 'title' TITLE-TEXT ')'
// TITLE-TEXT ::= MTEXT
pub struct Root {
    pub input_method: InputMethod,
    pub description: Option<Text>,
    pub title: Text,
    // variable_list: Vec<Variable>,
    // command_list: Vec<Command>,
    // module_list: Vec<Mgodule>,
    // macro_list: Vec<Macro>,
    pub maps: Vec<Map>,
    pub states: Vec<State>,
}

// IM-DECLARATION ::= '(' 'input-method' LANGUAGE NAME EXTRA-ID ? VERSION ? ')'
// LANGUAGE ::= SYMBOL
// NAME ::= SYMBOL
// EXTRA-ID ::= SYMBOL
// VERSION ::= '(' 'version' VERSION-NUMBER ')'
pub struct InputMethod {
    pub language: Symbol,
    pub name: Symbol,
    pub extra_id: Option<Symbol>,
    pub version: Option<String>,
}

// MAP-LIST ::= MAP-INCLUSION ? '(' 'map' MAP * ')'
// MAP ::= '(' MAP-NAME RULE * ')'
// MAP-NAME ::= SYMBOL
pub struct Map {
    pub name: Symbol,
    pub rules: Vec<Rule>,
}

// RULE ::= '(' KEYSEQ MAP-ACTION * ')'
pub struct Rule {
    /// Keys to press to produce mapping
    pub keyseq: KeySeq,
    /// The mapping describes what to do
    ///
    /// Right now we only support inserting characters.
    pub action: MapAction,
}

// KEYSEQ ::= MTEXT | '(' [ SYMBOL | INTEGER ] * ')'
pub enum KeySeq {
    /// > MTEXT in the definition of `KEYSEQ` consists of characters that can be
    /// > generated by a keyboard. Therefore MTEXT usually contains only ASCII
    /// > characters. However, if the input method is intended to be used, for
    /// > instance, with a West European keyboard, MTEXT may contain Latin-1
    /// > characters.
    Character(Text),
    /// > If the shift, control, meta, alt, super, and hyper modifiers are used,
    /// > they are represented by the `S-`, `C-`, `M-`, `A-`, `s-`, and `H-`
    /// > prefixes respectively in this order. Thus, "return with shift with
    /// > meta with hyper" is `(S-M-H-Return)`. Note that "a with shift" .. "z
    /// > with shift" are represented simply as `A .. Z`. Thus "a with shift
    /// > with meta with hyper" is `(M-H-A)`.
    KeyCombo(KeyCombo),
}

pub struct KeyCombo {
    /// Represents something like `C-S` for "Control + Shift"
    pub modifiers: Vec<Modifier>,
    // Can be TEXT or INTEGER but let's be conservative and expect a character
    // code.
    pub key: KeyDef,
}

pub enum KeyDef {
    /// > INTEGER in the definition of `KEYSEQ` must be a valid character code.
    CharacterCode(Integer),
    /// > SYMBOL in the definition of `KEYSEQ` must be the return value of the
    /// > `minput_event_to_key()` function. Under the X window system, you can
    /// > quickly check the value using the `xev` command. For example, the
    /// > return key, the backspace key, and the 0 key on the keypad are
    /// > represented as `(Return)`, `(BackSpace)`, and `(KP_0)` respectively.
    Character(Symbol),
}

/// Modifier keys
///
/// > "S-" (Shift), "C-" (Control), "M-" (Meta), "A-" (Alt), "G-" (AltGr), "s-"
/// > (Super), and "H-" (Hyper)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(strum_macros::EnumString, strum_macros::Display)]
#[strum(serialize_all = "snake_case")]
pub enum Modifier {
    Shift,
    #[strum(serialize = "ctrl", serialize = "control")]
    Control,
    /// Often one of the Alt keys in terminals
    Meta,
    Alt,
    AltGr,
    /// Super key is assumed to be Command key on macOS
    #[strum(serialize = "super", serialize = "cmd")]
    Super,
    /// Mo layers, mo fun. Let's pick caps lock for this.
    #[strum(serialize = "hyper", serialize = "caps")]
    Hyper,
}

impl Modifier {
    /// Parse kbdgen-typical key combo syntax
    ///
    /// ```rust
    /// use kbdgen::m17n_mim::Modifier;
    ///
    /// assert_eq!(
    ///     Modifier::parse_keycombo("ctrl").unwrap(),
    ///     vec![Modifier::Control],
    /// );
    /// assert_eq!(
    ///     Modifier::parse_keycombo("alt+shift").unwrap(),
    ///     vec![Modifier::Alt, Modifier::Shift],
    /// );
    /// assert_eq!(
    ///     Modifier::parse_keycombo("cmd+alt").unwrap(),
    ///     vec![Modifier::Super, Modifier::Alt],
    /// );
    /// ```
    pub fn parse_keycombo(input: &str) -> Result<Vec<Modifier>, MimConversion> {
        let mut res = vec![];
        for m in input.split('+') {
            if let Ok(m) = Modifier::from_str(m) {
                res.push(m);
            } else {
                InvalidKeyCombo {
                    input: format!("unknown modifier `{}`", m),
                }
                .fail()?;
            }
        }
        Ok(res)
    }
}

// Originally a quite complex type, defined as:
//
// ```bnf
// MAP-ACTION ::= ACTION
//
// ACTION ::= INSERT | DELETE | SELECT | MOVE | MARK
//            | SHOW | HIDE | PUSHBACK | POP | UNDO
// 	          | COMMIT | UNHANDLE | SHIFT | CALL
// 	          | SET | IF | COND | '(' MACRO-NAME ')'
//
// PREDEFINED-SYMBOL ::=
//     '@0' | '@1' | '@2' | '@3' | '@4'
//     | '@5' | '@6' | '@7' | '@8' | '@9'
//     | '@<' | '@=' | '@>' | '@-' | '@+' | '@[' | '@]'
//     | '@@'
//     | '@-0' | '@-N' | '@+N'
// ```
//
// but we've only implemented the trivial Insert variants at the moment.
pub enum MapAction {
    Insert(Insert),

    #[cfg(sorry_not_yet_implemented)]
    Delete(Delete),
    #[cfg(sorry_not_yet_implemented)]
    Select(Select),
    #[cfg(sorry_not_yet_implemented)]
    Move(Move),
    #[cfg(sorry_not_yet_implemented)]
    Mark(Mark),
    #[cfg(sorry_not_yet_implemented)]
    Show(Show),
    #[cfg(sorry_not_yet_implemented)]
    Hide(Hide),
    #[cfg(sorry_not_yet_implemented)]
    Pushback(Pushback),
    #[cfg(sorry_not_yet_implemented)]
    Pop(Pop),
    #[cfg(sorry_not_yet_implemented)]
    Undo(Undo),
    #[cfg(sorry_not_yet_implemented)]
    Commit(Commit),
    #[cfg(sorry_not_yet_implemented)]
    Unhandle(Unhandle),
    #[cfg(sorry_not_yet_implemented)]
    Shift(Shift),
    #[cfg(sorry_not_yet_implemented)]
    Call(Call),
    #[cfg(sorry_not_yet_implemented)]
    Set(Set),
    #[cfg(sorry_not_yet_implemented)]
    If(If),
    #[cfg(sorry_not_yet_implemented)]
    Cond(Cond),
    #[cfg(sorry_not_yet_implemented)]
    Macro(Symbol),
}

/// Insert action: insert something before the current position
//
// Originally defined as:
//
// ```bnf
// INSERT ::= '(' 'insert' MTEXT ')'
//             | MTEXT
//             | INTEGER
//             | SYMBOL
//             | '(' 'insert' SYMBOL ')'
//             | '(' 'insert' '(' CANDIDATES * ')' ')'
//             | '(' CANDIDATES * ')'
//
// CANDIDATES ::= MTEXT | '(' MTEXT * ')'
// ```
//
// but we'll only use the simple forms for now
pub enum Insert {
    /// > insert TEXT before the current position
    Character(Text),
    /// > inserts the character INTEGER before the current position
    CharacterCode(Integer),

    #[cfg(sorry_not_yet_implemented)]
    /// > treats SYMBOL as a variable, and inserts its value (if it is a valid
    /// > character code) before the current position
    KeyCombo(Symbol),

    #[cfg(sorry_not_yet_implemented)]
    /// > each CANDIDATES represents a candidate group, and each element of
    /// >CANDIDATES represents a candidate, i.e. if CANDIDATES is an M-text, the
    /// >candidates are the characters in the M-text; if CANDIDATES is a list of
    /// >M-texts, the candidates are the M-texts in the list.
    /// >
    /// >These forms insert the first candidate before the current position. The
    /// >inserted string is associated with the list of candidates and the
    /// >information indicating the currently selected candidate.
    Candidates(Vec<Text>),
}

// STATE-LIST ::= STATE-INCUSION ? '(' 'state' STATE * ')'  STATE-INCUSION ?
// STATE ::= '(' STATE-NAME [ STATE-TITLE-TEXT ] BRANCH * ')'
// STATE-NAME ::= SYMBOL
// STATE-TITLE-TEXT ::= MTEXT
// STATE-INCLUSION ::= '(' 'include' TAGS 'state' STATE-NAME ? ')'
pub struct State {
    pub name: Symbol,
    pub title: Option<Text>,
    pub branches: Vec<Branch>,
}

// BRANCH ::= '(' MAP-NAME BRANCH-ACTION * ')'
// 	   | '(' 'nil' BRANCH-ACTION * ')'
// 	   | '(' 't' BRANCH-ACTION * ')'
pub struct Branch {
    pub map_name: Symbol,

    #[cfg(sorry_not_yet_implemented)]
    pub actions: Vec<Action>,
}

/// The "MSymbol" type
///
/// [Defined](https://www.nongnu.org/m17n/manual-en/m17nDBFormat.html) as:
///
/// > An element that matches the regular expression `[^-0-9(]([^\()]|\.)+`
/// > represents a property whose key is `Msymbol`. In the element, `\t`, `\n`,
/// > `\r`, and `\e` are replaced with tab (code 9), newline (code 10), carriage
/// > return (code 13), and escape (code 27) respectively. Other characters
/// > following a backslash is interpreted as it is. The value of the property
/// > is the symbol having the resulting string as its name.
/// >
/// > For instance, the element `abc\ def` represents a property whose value is
/// > the symbol having the name "abc def".
#[derive(Clone, Debug)]
pub struct Symbol(String);

impl TryFrom<String> for Symbol {
    type Error = MimConversion;

    fn try_from(x: String) -> Result<Self, Self::Error> {
        // FIXME: Escaping and stuff
        Ok(Symbol(x))
    }
}

/// The "MText" type
///
/// [Defined](https://www.nongnu.org/m17n/manual-en/m17nDBFormat.html) as:
///
/// > An element that matches the regular expression `([^"]|\")*` represents a
/// > property whose key is `Mtext`. The backslash escape explained above also
/// > applies here. Moreover, each part in the element matching the regular
/// > expression `\[xX][0-9A-Fa-f][0-9A-Fa-f]` is replaced with its hexadecimal
/// > interpretation.
///
/// > After having resolved the backslash escapes, the byte sequence between the
/// > double quotes is interpreted as a UTF-8 sequence and decoded into an
/// > M-text. This M-text is the value of the property.
#[derive(Clone, Debug)]
pub struct Text(String);

impl TryFrom<String> for Text {
    type Error = MimConversion;

    fn try_from(input: String) -> Result<Self, Self::Error> {
        // FIXME: Escaping and stuff
        // FIXME: transforming existing escapes like `\u{30A}` to `\x30A`

        lazy_static::lazy_static! {
            static ref RE: Regex = Regex::new(r"\\u\{([0-9A-Fa-f]{1,6})\}").expect("valid regex");
        }

        let new = RE.replace_all(&input, |cap: &regex::Captures| {
            let hex = cap.get(1).unwrap().as_str();
            format!("\\x{}", hex)
        });

        Ok(Text(new.to_string()))
    }
}

/// The "Minteger" type
///
/// [Defined](https://www.nongnu.org/m17n/manual-en/m17nDBFormat.html) as:
///
/// > An element that matches the regular expression `-?[0-9]+ or
/// > 0[xX][0-9A-Fa-f]+` represents a property whose key is `Minteger`. An
/// > element matching the former expression is interpreted as an integer in
/// > decimal notation, and one matching the latter is interpreted as an integer
/// > in hexadecimal notation. The value of the property is the result of
/// > interpretation.
/// >
/// > For instance, the element `0xA0` represents a property whose value is 160
/// > in decimal.
#[derive(Clone, Debug)]
pub struct Integer(String);

impl TryFrom<String> for Integer {
    type Error = MimConversion;

    fn try_from(input: String) -> Result<Self, Self::Error> {
        // decimal
        if u32::from_str_radix(&input, 10).is_ok() {
            return Ok(Integer(input));
        }

        // hex
        if !(input.starts_with("0x") || input.starts_with("0X")) {
            return InvalidIntegerHexPrefix { input }.fail();
        }
        match u64::from_str_radix(&input[2..], 16) {
            Ok(_) => Ok(Integer(input)),
            Err(source) => Err(InvalidIntegerHexValue { input }.into_error(source)),
        }
    }
}

/// Possible errors when converting values to their MIM representation
#[derive(snafu::Snafu, Debug)]
pub enum MimConversion {
    #[snafu(display("Could serialize MIM symbol"))]
    InvalidSymbol { backtrace: snafu::Backtrace },
    #[snafu(display("Could serialize MIM text"))]
    InvalidText { backtrace: snafu::Backtrace },
    #[snafu(display("Could serialize MIM key combo: {}", input))]
    InvalidKeyCombo {
        input: String,
        backtrace: snafu::Backtrace,
    },
    #[snafu(display("Could not map index `{}` to a character code", index))]
    InvalidCharactorCodeIndex {
        index: usize,
        backtrace: snafu::Backtrace,
    },
    #[snafu(display(
        "Assumed hexadecimal MIM integer but there was no `0x` prefix in `{}`",
        input
    ))]
    InvalidIntegerHexPrefix {
        input: String,
        backtrace: snafu::Backtrace,
    },
    #[snafu(display(
        "Assumed hexadecimal MIM integer but `{}` is not a valid hex value",
        input
    ))]
    InvalidIntegerHexValue {
        input: String,
        source: std::num::ParseIntError,
        backtrace: snafu::Backtrace,
    },
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate criterion;
use criterion::{black_box, Criterion};

use rsvg_internals::rect::IRect;
use rsvg_internals::surface_utils::{
    iterators::Pixels,
    shared_surface::{SharedImageSurface, SurfaceType},
};

const SURFACE_SIDE: i32 = 512;
const BOUNDS: IRect = IRect {
    x0: 64,
    y0: 32,
    x1: 448,
    y1: 480,
};

fn bench_pixel_iterators(c: &mut Criterion) {
    c.bench_function("pixel_iterators straightforward", |b| {
        let mut surface =
            cairo::ImageSurface::create(cairo::Format::ARgb32, SURFACE_SIDE, SURFACE_SIDE).unwrap();
        let stride = surface.get_stride();
        let data = surface.get_data().unwrap();

        let bounds = black_box(BOUNDS);

        b.iter(|| {
            let mut r = 0usize;
            let mut g = 0usize;
            let mut b = 0usize;
            let mut a = 0usize;

            for y in bounds.y_range() {
                for x in bounds.x_range() {
                    let base = (y * stride + x * 4) as usize;

                    r += data[base + 0] as usize;
                    g += data[base + 1] as usize;
                    b += data[base + 2] as usize;
                    a += data[base + 3] as usize;
                }
            }

            (r, g, b, a)
        })
    });

    c.bench_function("pixel_iterators get_pixel", |b| {
        let surface =
            cairo::ImageSurface::create(cairo::Format::ARgb32, SURFACE_SIDE, SURFACE_SIDE).unwrap();
        let surface = SharedImageSurface::wrap(surface, SurfaceType::SRgb).unwrap();

        let bounds = black_box(BOUNDS);

        b.iter(|| {
            let mut r = 0usize;
            let mut g = 0usize;
            let mut b = 0usize;
            let mut a = 0usize;

            for y in bounds.y_range() {
                for x in bounds.x_range() {
                    let pixel = surface.get_pixel(x as u32, y as u32);

                    r += pixel.r as usize;
                    g += pixel.g as usize;
                    b += pixel.b as usize;
                    a += pixel.a as usize;
                }
            }

            (r, g, b, a)
        })
    });

    c.bench_function("pixel_iterators pixels", |b| {
        let surface =
            cairo::ImageSurface::create(cairo::Format::ARgb32, SURFACE_SIDE, SURFACE_SIDE).unwrap();
        let data = SharedImageSurface::wrap(surface, SurfaceType::SRgb).unwrap();

        let bounds = black_box(BOUNDS);

        b.iter(|| {
            let mut r = 0usize;
            let mut g = 0usize;
            let mut b = 0usize;
            let mut a = 0usize;

            for (_x, _y, pixel) in Pixels::within(&data, bounds) {
                r += pixel.r as usize;
                g += pixel.g as usize;
                b += pixel.b as usize;
                a += pixel.a as usize;
            }

            (r, g, b, a)
        })
    });
}

criterion_group!(benches, bench_pixel_iterators);
criterion_main!(benches);
<commit_msg>benches: port pixel iterator bench to ExclusiveImageSurface<commit_after>#[macro_use]
extern crate criterion;
use criterion::{black_box, Criterion};

use rsvg_internals::rect::IRect;
use rsvg_internals::surface_utils::{
    iterators::Pixels,
    shared_surface::{ExclusiveImageSurface, SharedImageSurface, SurfaceType},
};

const SURFACE_SIDE: i32 = 512;
const BOUNDS: IRect = IRect {
    x0: 64,
    y0: 32,
    x1: 448,
    y1: 480,
};

fn bench_pixel_iterators(c: &mut Criterion) {
    c.bench_function("pixel_iterators straightforward", |b| {
        let mut surface =
            ExclusiveImageSurface::new(SURFACE_SIDE, SURFACE_SIDE, SurfaceType::SRgb).unwrap();
        let stride = surface.stride() as i32;
        let data = surface.get_data();

        let bounds = black_box(BOUNDS);

        b.iter(|| {
            let mut r = 0usize;
            let mut g = 0usize;
            let mut b = 0usize;
            let mut a = 0usize;

            for y in bounds.y_range() {
                for x in bounds.x_range() {
                    let base = (y * stride + x * 4) as usize;

                    r += data[base + 0] as usize;
                    g += data[base + 1] as usize;
                    b += data[base + 2] as usize;
                    a += data[base + 3] as usize;
                }
            }

            (r, g, b, a)
        })
    });

    c.bench_function("pixel_iterators get_pixel", |b| {
        let surface =
            SharedImageSurface::empty(SURFACE_SIDE, SURFACE_SIDE, SurfaceType::SRgb).unwrap();

        let bounds = black_box(BOUNDS);

        b.iter(|| {
            let mut r = 0usize;
            let mut g = 0usize;
            let mut b = 0usize;
            let mut a = 0usize;

            for y in bounds.y_range() {
                for x in bounds.x_range() {
                    let pixel = surface.get_pixel(x as u32, y as u32);

                    r += pixel.r as usize;
                    g += pixel.g as usize;
                    b += pixel.b as usize;
                    a += pixel.a as usize;
                }
            }

            (r, g, b, a)
        })
    });

    c.bench_function("pixel_iterators pixels", |b| {
        let surface =
            SharedImageSurface::empty(SURFACE_SIDE, SURFACE_SIDE, SurfaceType::SRgb).unwrap();

        let bounds = black_box(BOUNDS);

        b.iter(|| {
            let mut r = 0usize;
            let mut g = 0usize;
            let mut b = 0usize;
            let mut a = 0usize;

            for (_x, _y, pixel) in Pixels::within(&surface, bounds) {
                r += pixel.r as usize;
                g += pixel.g as usize;
                b += pixel.b as usize;
                a += pixel.a as usize;
            }

            (r, g, b, a)
        })
    });
}

criterion_group!(benches, bench_pixel_iterators);
criterion_main!(benches);
<|endoftext|>