<commit_before>use core;
use core::{pass, image, memory};
use core::format::Format;
use metal::*;

// The boolean indicates whether this is a depth format
pub fn map_format(format: Format) -> Option<(MTLPixelFormat, bool)> {
    use core::format::SurfaceType::*;
    use core::format::ChannelType::*;

    // TODO: more formats
    match format {
        Format(R8_G8_B8_A8, Unorm) => Some((MTLPixelFormat::RGBA8Unorm, false)),
        Format(R8_G8_B8_A8, Srgb) => Some((MTLPixelFormat::RGBA8Unorm_sRGB, false)),
        Format(B8_G8_R8_A8, Unorm) => Some((MTLPixelFormat::BGRA8Unorm, false)),
        Format(B8_G8_R8_A8, Srgb) => Some((MTLPixelFormat::BGRA8Unorm_sRGB, false)),
        _ => None,
    }
}

pub fn get_format_bytes_per_pixel(format: MTLPixelFormat) -> usize {
    // TODO: more formats
    match format {
        MTLPixelFormat::RGBA8Unorm => 4,
        MTLPixelFormat::RGBA8Unorm_sRGB => 4,
        MTLPixelFormat::BGRA8Unorm => 4,
        _ => unimplemented!(),
    }
}

pub fn map_load_operation(operation: pass::AttachmentLoadOp) -> MTLLoadAction {
    use self::pass::AttachmentLoadOp::*;

    match operation {
        Load => MTLLoadAction::Load,
        Clear => MTLLoadAction::Clear,
        DontCare => MTLLoadAction::DontCare,
    }
}

pub fn map_store_operation(operation: pass::AttachmentStoreOp) -> MTLStoreAction {
    use self::pass::AttachmentStoreOp::*;

    match operation {
        Store => MTLStoreAction::Store,
        DontCare => MTLStoreAction::DontCare,
    }
}

pub fn map_write_mask(mask: core::state::ColorMask) -> MTLColorWriteMask {
    use core::state;

    let mut mtl_mask = MTLColorWriteMaskNone;

    if mask.contains(state::RED) {
        mtl_mask |= MTLColorWriteMaskRed;
    }
    if mask.contains(state::GREEN) {
        mtl_mask |= MTLColorWriteMaskGreen;
    }
    if mask.contains(state::BLUE) {
        mtl_mask |= MTLColorWriteMaskBlue;
    }
    if mask.contains(state::ALPHA) {
        mtl_mask |= MTLColorWriteMaskAlpha;
    }

    mtl_mask
}

pub fn map_blend_op(equation: core::state::Equation) -> MTLBlendOperation {
    use core::state::Equation::*;

    match equation {
        Add => MTLBlendOperation::Add,
        Sub => MTLBlendOperation::Subtract,
        RevSub => MTLBlendOperation::ReverseSubtract,
        Min => MTLBlendOperation::Min,
        Max => MTLBlendOperation::Max,
    }
}

pub fn map_blend_factor(factor: core::state::Factor, scalar: bool) -> MTLBlendFactor {
    use core::state::BlendValue::*;
    use core::state::Factor::*;

    match factor {
        Zero => MTLBlendFactor::Zero,
        One => MTLBlendFactor::One,
        SourceAlphaSaturated => MTLBlendFactor::SourceAlphaSaturated,
        ZeroPlus(SourceColor) if !scalar => MTLBlendFactor::SourceColor,
        ZeroPlus(SourceAlpha) => MTLBlendFactor::SourceAlpha,
        ZeroPlus(DestColor) if !scalar => MTLBlendFactor::DestinationColor,
        ZeroPlus(DestAlpha) => MTLBlendFactor::DestinationAlpha,
        ZeroPlus(ConstColor) if !scalar => MTLBlendFactor::BlendColor,
        ZeroPlus(ConstAlpha) => MTLBlendFactor::BlendAlpha,
        OneMinus(SourceColor) if !scalar => MTLBlendFactor::OneMinusSourceColor,
        OneMinus(SourceAlpha) => MTLBlendFactor::OneMinusSourceAlpha,
        OneMinus(DestColor) if !scalar => MTLBlendFactor::OneMinusDestinationColor,
        OneMinus(DestAlpha) => MTLBlendFactor::OneMinusDestinationAlpha,
        OneMinus(ConstColor) if !scalar => MTLBlendFactor::OneMinusBlendColor,
        OneMinus(ConstAlpha) => MTLBlendFactor::OneMinusBlendAlpha,
        _ => {
            error!("Invalid blend factor requested for {}: {:?}",
                if scalar {"alpha"} else {"color"}, factor);
            MTLBlendFactor::Zero
        }
    }
}

pub fn map_vertex_format(format: Format) -> Option<MTLVertexFormat> {
    use core::format::SurfaceType::*;
    use core::format::ChannelType::*;

    // TODO: more formats
    Some(match format {
        Format(R32_G32_B32_A32, Float) => MTLVertexFormat::Float4,
        Format(R32_G32_B32, Float) => MTLVertexFormat::Float3,
        Format(R32_G32, Float) => MTLVertexFormat::Float2,
        _ => return None,
    })
}

pub fn map_memory_properties_to_options(properties: memory::Properties) -> MTLResourceOptions {
    let mut options = MTLResourceOptions::empty();
    if properties.contains(memory::CPU_VISIBLE) {
        if properties.contains(memory::COHERENT) {
            options |= MTLResourceStorageModeShared;
        } else {
            options |= MTLResourceStorageModeManaged;
        }
    } else if properties.contains(memory::DEVICE_LOCAL) {
        options |= MTLResourceStorageModePrivate;
    } else {
        panic!("invalid heap properties");
    }
    if properties.contains(memory::WRITE_COMBINED) {
        options |= MTLResourceOptionCPUCacheModeWriteCombined;
    }
    options
}

pub fn map_memory_properties_to_storage_and_cache(properties: memory::Properties) -> (MTLStorageMode, MTLCPUCacheMode) {
    let storage = if properties.contains(memory::CPU_VISIBLE) {
        if properties.contains(memory::COHERENT) {
            MTLStorageMode::Shared
        } else {
            MTLStorageMode::Managed
        }
    } else if properties.contains(memory::DEVICE_LOCAL) {
        MTLStorageMode::Private
    } else {
        panic!("invalid heap properties");
    };
    let cpu = if properties.contains(memory::WRITE_COMBINED) {
        MTLCPUCacheMode::WriteCombined
    } else {
        MTLCPUCacheMode::DefaultCache
    };
    (storage, cpu)
}

pub fn resource_options_from_storage_and_cache(storage: MTLStorageMode, cache: MTLCPUCacheMode) -> MTLResourceOptions {
    MTLResourceOptions::from_bits(
        ((storage as u64) << MTLResourceStorageModeShift) | ((cache as u64) << MTLResourceCPUCacheModeShift)
    ).unwrap()
}

pub fn map_texture_usage(usage: image::Usage) -> MTLTextureUsage {
    let mut texture_usage = MTLTextureUsagePixelFormatView;
    if usage.contains(image::COLOR_ATTACHMENT) || usage.contains(image::DEPTH_STENCIL_ATTACHMENT) {
        texture_usage |= MTLTextureUsageRenderTarget;
    }
    if usage.contains(image::SAMPLED) {
        texture_usage |= MTLTextureUsageShaderRead;
    }
    // TODO shader write
    texture_usage
}
<commit_msg>Fix metal memory heap properties<commit_after>use core;
use core::{pass, image, memory};
use core::format::Format;
use metal::*;

// The boolean indicates whether this is a depth format
pub fn map_format(format: Format) -> Option<(MTLPixelFormat, bool)> {
    use core::format::SurfaceType::*;
    use core::format::ChannelType::*;

    // TODO: more formats
    match format {
        Format(R8_G8_B8_A8, Unorm) => Some((MTLPixelFormat::RGBA8Unorm, false)),
        Format(R8_G8_B8_A8, Srgb) => Some((MTLPixelFormat::RGBA8Unorm_sRGB, false)),
        Format(B8_G8_R8_A8, Unorm) => Some((MTLPixelFormat::BGRA8Unorm, false)),
        Format(B8_G8_R8_A8, Srgb) => Some((MTLPixelFormat::BGRA8Unorm_sRGB, false)),
        _ => None,
    }
}

pub fn get_format_bytes_per_pixel(format: MTLPixelFormat) -> usize {
    // TODO: more formats
    match format {
        MTLPixelFormat::RGBA8Unorm => 4,
        MTLPixelFormat::RGBA8Unorm_sRGB => 4,
        MTLPixelFormat::BGRA8Unorm => 4,
        _ => unimplemented!(),
    }
}

pub fn map_load_operation(operation: pass::AttachmentLoadOp) -> MTLLoadAction {
    use self::pass::AttachmentLoadOp::*;

    match operation {
        Load => MTLLoadAction::Load,
        Clear => MTLLoadAction::Clear,
        DontCare => MTLLoadAction::DontCare,
    }
}

pub fn map_store_operation(operation: pass::AttachmentStoreOp) -> MTLStoreAction {
    use self::pass::AttachmentStoreOp::*;

    match operation {
        Store => MTLStoreAction::Store,
        DontCare => MTLStoreAction::DontCare,
    }
}

pub fn map_write_mask(mask: core::state::ColorMask) -> MTLColorWriteMask {
    use core::state;

    let mut mtl_mask = MTLColorWriteMaskNone;

    if mask.contains(state::RED) {
        mtl_mask |= MTLColorWriteMaskRed;
    }
    if mask.contains(state::GREEN) {
        mtl_mask |= MTLColorWriteMaskGreen;
    }
    if mask.contains(state::BLUE) {
        mtl_mask |= MTLColorWriteMaskBlue;
    }
    if mask.contains(state::ALPHA) {
        mtl_mask |= MTLColorWriteMaskAlpha;
    }

    mtl_mask
}

pub fn map_blend_op(equation: core::state::Equation) -> MTLBlendOperation {
    use core::state::Equation::*;

    match equation {
        Add => MTLBlendOperation::Add,
        Sub => MTLBlendOperation::Subtract,
        RevSub => MTLBlendOperation::ReverseSubtract,
        Min => MTLBlendOperation::Min,
        Max => MTLBlendOperation::Max,
    }
}

pub fn map_blend_factor(factor: core::state::Factor, scalar: bool) -> MTLBlendFactor {
    use core::state::BlendValue::*;
    use core::state::Factor::*;

    match factor {
        Zero => MTLBlendFactor::Zero,
        One => MTLBlendFactor::One,
        SourceAlphaSaturated => MTLBlendFactor::SourceAlphaSaturated,
        ZeroPlus(SourceColor) if !scalar => MTLBlendFactor::SourceColor,
        ZeroPlus(SourceAlpha) => MTLBlendFactor::SourceAlpha,
        ZeroPlus(DestColor) if !scalar => MTLBlendFactor::DestinationColor,
        ZeroPlus(DestAlpha) => MTLBlendFactor::DestinationAlpha,
        ZeroPlus(ConstColor) if !scalar => MTLBlendFactor::BlendColor,
        ZeroPlus(ConstAlpha) => MTLBlendFactor::BlendAlpha,
        OneMinus(SourceColor) if !scalar => MTLBlendFactor::OneMinusSourceColor,
        OneMinus(SourceAlpha) => MTLBlendFactor::OneMinusSourceAlpha,
        OneMinus(DestColor) if !scalar => MTLBlendFactor::OneMinusDestinationColor,
        OneMinus(DestAlpha) => MTLBlendFactor::OneMinusDestinationAlpha,
        OneMinus(ConstColor) if !scalar => MTLBlendFactor::OneMinusBlendColor,
        OneMinus(ConstAlpha) => MTLBlendFactor::OneMinusBlendAlpha,
        _ => {
            error!("Invalid blend factor requested for {}: {:?}",
                if scalar {"alpha"} else {"color"}, factor);
            MTLBlendFactor::Zero
        }
    }
}

pub fn map_vertex_format(format: Format) -> Option<MTLVertexFormat> {
    use core::format::SurfaceType::*;
    use core::format::ChannelType::*;

    // TODO: more formats
    Some(match format {
        Format(R32_G32_B32_A32, Float) => MTLVertexFormat::Float4,
        Format(R32_G32_B32, Float) => MTLVertexFormat::Float3,
        Format(R32_G32, Float) => MTLVertexFormat::Float2,
        _ => return None,
    })
}

pub fn map_memory_properties_to_options(properties: memory::Properties) -> MTLResourceOptions {
    let mut options = MTLResourceOptions::empty();
    if properties.contains(memory::CPU_VISIBLE) {
        if properties.contains(memory::COHERENT) {
            options |= MTLResourceStorageModeShared;
        } else {
            options |= MTLResourceStorageModeManaged;
        }
    } else if properties.contains(memory::DEVICE_LOCAL) {
        options |= MTLResourceStorageModePrivate;
    } else {
        panic!("invalid heap properties");
    }
    if !properties.contains(memory::CPU_CACHED) {
        options |= MTLResourceCPUCacheModeWriteCombined;
    }
    options
}

pub fn map_memory_properties_to_storage_and_cache(properties: memory::Properties) -> (MTLStorageMode, MTLCPUCacheMode) {
    let storage = if properties.contains(memory::CPU_VISIBLE) {
        if properties.contains(memory::COHERENT) {
            MTLStorageMode::Shared
        } else {
            MTLStorageMode::Managed
        }
    } else if properties.contains(memory::DEVICE_LOCAL) {
        MTLStorageMode::Private
    } else {
        panic!("invalid heap properties");
    };
    let cpu = if properties.contains(memory::CPU_CACHED) {
        MTLCPUCacheMode::DefaultCache
    } else {
        MTLCPUCacheMode::WriteCombined
    };
    (storage, cpu)
}

pub fn resource_options_from_storage_and_cache(storage: MTLStorageMode, cache: MTLCPUCacheMode) -> MTLResourceOptions {
    MTLResourceOptions::from_bits(
        ((storage as u64) << MTLResourceStorageModeShift) | ((cache as u64) << MTLResourceCPUCacheModeShift)
    ).unwrap()
}

pub fn map_texture_usage(usage: image::Usage) -> MTLTextureUsage {
    let mut texture_usage = MTLTextureUsagePixelFormatView;
    if usage.contains(image::COLOR_ATTACHMENT) || usage.contains(image::DEPTH_STENCIL_ATTACHMENT) {
        texture_usage |= MTLTextureUsageRenderTarget;
    }
    if usage.contains(image::SAMPLED) {
        texture_usage |= MTLTextureUsageShaderRead;
    }
    // TODO shader write
    texture_usage
}
<|endoftext|>


<commit_before>use byteorder::{LittleEndian as E, WriteBytesExt};

use std::io::{Result as IoResult, Seek, SeekFrom};

const TY_ASCII: u16 = 2;
const TY_SHORT: u16 = 3;
const TY_LONG: u16 = 4;
const TAG_IMAGE_WIDTH: u16 = 0x100;
const TAG_IMAGE_LENGTH: u16 = 0x101;
const TAG_BITS_PER_SAMPLE: u16 = 0x102;
const TAG_IMAGE_DESCRIPTION: u16 = 0x10E;
const TAG_STRIP_OFFSETS: u16 = 0x111;
const TAG_ROWS_PER_STRIP: u16 = 0x116;
const TAG_STRIP_BYTE_COUNTS: u16 = 0x117;

struct Field {
    tag: u16,
    ty: u16,
    count: u32,
    value: u32,
}

pub struct Image<'a> {
    pub width: u32,
    pub height: u32,
    pub bpp: u16,
    pub name: &'a str,
    pub data: &'a [u8],
}

pub fn save<W: Seek + WriteBytesExt>(mut tiff: W, images: &[Image]) -> IoResult<()> {
    // header
    tiff.write_u16::<E>(0x4949)?; // little endian
    tiff.write_u16::<E>(42)?; // magic
    let data_start = (images.len() * 0x80) as u32;
    let mut data_offset = data_start;
    let mut cur_offset = 4;
    // image file directory
    for im in images {
        tiff.write_u32::<E>(cur_offset + 4)?; // IFD offset
        let total_bytes = (im.width * im.height) as usize * im.bpp as usize / 8;
        assert_eq!(total_bytes, im.data.len());
        let description: u32 = im
            .name
            .chars()
            .take(3)
            .enumerate()
            .map(|(i, c)| (c as u32) << (i * 8))
            .sum();
        let fields = [
            Field {
                tag: TAG_IMAGE_WIDTH,
                ty: TY_LONG,
                count: 1,
                value: im.width,
            },
            Field {
                tag: TAG_IMAGE_LENGTH,
                ty: TY_LONG,
                count: 1,
                value: im.height,
            },
            Field {
                tag: TAG_BITS_PER_SAMPLE,
                ty: TY_SHORT,
                count: 1,
                value: im.bpp as u32,
            },
            Field {
                tag: TAG_IMAGE_DESCRIPTION,
                ty: TY_ASCII,
                count: im.name.len().min(3) as u32 + 1,
                value: description,
            },
            Field {
                tag: TAG_STRIP_OFFSETS,
                ty: TY_LONG,
                count: 1,
                value: data_offset,
            },
            Field {
                tag: TAG_ROWS_PER_STRIP,
                ty: TY_SHORT,
                count: 1,
                value: im.height,
            },
            Field {
                tag: TAG_STRIP_BYTE_COUNTS,
                ty: TY_LONG,
                count: 1,
                value: total_bytes as u32,
            },
        ];
        tiff.write_u16::<E>(fields.len() as u16)?;
        for &Field {
            tag,
            ty,
            count,
            value,
        } in &fields
        {
            tiff.write_u16::<E>(tag)?;
            tiff.write_u16::<E>(ty)?;
            tiff.write_u32::<E>(count)?;
            tiff.write_u32::<E>(value)?;
        }
        cur_offset += 4 + 2 + fields.len() as u32 * 12;
        data_offset += total_bytes as u32;
        assert_eq!(tiff.seek(SeekFrom::Current(0)).unwrap(), cur_offset as u64);
    }
    // gap
    assert!(cur_offset < data_start);
    tiff.write_u32::<E>(0)?; // next IFD offset
    for _ in cur_offset + 4..data_start {
        tiff.write_u8(0)?;
    }
    // image data
    for im in images {
        tiff.write(im.data)?;
    }
    Ok(())
}
<commit_msg>Fix a clippy warning<commit_after>use byteorder::{LittleEndian as E, WriteBytesExt};

use std::io::{Result as IoResult, Seek, SeekFrom};

const TY_ASCII: u16 = 2;
const TY_SHORT: u16 = 3;
const TY_LONG: u16 = 4;
const TAG_IMAGE_WIDTH: u16 = 0x100;
const TAG_IMAGE_LENGTH: u16 = 0x101;
const TAG_BITS_PER_SAMPLE: u16 = 0x102;
const TAG_IMAGE_DESCRIPTION: u16 = 0x10E;
const TAG_STRIP_OFFSETS: u16 = 0x111;
const TAG_ROWS_PER_STRIP: u16 = 0x116;
const TAG_STRIP_BYTE_COUNTS: u16 = 0x117;

struct Field {
    tag: u16,
    ty: u16,
    count: u32,
    value: u32,
}

pub struct Image<'a> {
    pub width: u32,
    pub height: u32,
    pub bpp: u16,
    pub name: &'a str,
    pub data: &'a [u8],
}

pub fn save<W: Seek + WriteBytesExt>(mut tiff: W, images: &[Image]) -> IoResult<()> {
    // header
    tiff.write_u16::<E>(0x4949)?; // little endian
    tiff.write_u16::<E>(42)?; // magic
    let data_start = (images.len() * 0x80) as u32;
    let mut data_offset = data_start;
    let mut cur_offset = 4;
    // image file directory
    for im in images {
        tiff.write_u32::<E>(cur_offset + 4)?; // IFD offset
        let total_bytes = (im.width * im.height) as usize * im.bpp as usize / 8;
        assert_eq!(total_bytes, im.data.len());
        let description: u32 = im
            .name
            .chars()
            .take(3)
            .enumerate()
            .map(|(i, c)| (c as u32) << (i * 8))
            .sum();
        let fields = [
            Field {
                tag: TAG_IMAGE_WIDTH,
                ty: TY_LONG,
                count: 1,
                value: im.width,
            },
            Field {
                tag: TAG_IMAGE_LENGTH,
                ty: TY_LONG,
                count: 1,
                value: im.height,
            },
            Field {
                tag: TAG_BITS_PER_SAMPLE,
                ty: TY_SHORT,
                count: 1,
                value: im.bpp as u32,
            },
            Field {
                tag: TAG_IMAGE_DESCRIPTION,
                ty: TY_ASCII,
                count: im.name.len().min(3) as u32 + 1,
                value: description,
            },
            Field {
                tag: TAG_STRIP_OFFSETS,
                ty: TY_LONG,
                count: 1,
                value: data_offset,
            },
            Field {
                tag: TAG_ROWS_PER_STRIP,
                ty: TY_SHORT,
                count: 1,
                value: im.height,
            },
            Field {
                tag: TAG_STRIP_BYTE_COUNTS,
                ty: TY_LONG,
                count: 1,
                value: total_bytes as u32,
            },
        ];
        tiff.write_u16::<E>(fields.len() as u16)?;
        for &Field {
            tag,
            ty,
            count,
            value,
        } in &fields
        {
            tiff.write_u16::<E>(tag)?;
            tiff.write_u16::<E>(ty)?;
            tiff.write_u32::<E>(count)?;
            tiff.write_u32::<E>(value)?;
        }
        cur_offset += 4 + 2 + fields.len() as u32 * 12;
        data_offset += total_bytes as u32;
        assert_eq!(tiff.seek(SeekFrom::Current(0)).unwrap(), cur_offset as u64);
    }
    // gap
    assert!(cur_offset < data_start);
    tiff.write_u32::<E>(0)?; // next IFD offset
    for _ in cur_offset + 4..data_start {
        tiff.write_u8(0)?;
    }
    // image data
    for im in images {
        tiff.write_all(im.data)?;
    }
    Ok(())
}
<|endoftext|>


<commit_before>#![recursion_limit = "1024"]

extern crate chrono;
extern crate hyper;
extern crate hyper_native_tls;

extern crate serde;

#[macro_use]
extern crate serde_derive;
extern crate serde_json;

#[macro_use]
extern crate log;

extern crate failure;
#[macro_use]
extern crate failure_derive;

pub use auth::Token;
pub use errors::*;
pub use region::*;

mod errors;
pub mod auth;
pub mod region;
pub mod client;
<commit_msg>remove unused warning<commit_after>#![recursion_limit = "1024"]

extern crate chrono;
extern crate hyper;
extern crate hyper_native_tls;

extern crate serde;

#[macro_use]
extern crate serde_derive;
extern crate serde_json;

#[macro_use]
extern crate log;

extern crate failure;
extern crate failure_derive;

pub use auth::Token;
pub use errors::*;
pub use region::*;

mod errors;
pub mod auth;
pub mod region;
pub mod client;
<|endoftext|>


<commit_before>// Copyright (C) 2020 Julen Ruiz Aizpuru, https://github.com/julen

//! # Plurr
//!
//! A library for handling plurals/genders/conditionals.

use std::collections::HashMap;

mod plurals;
mod utils;

const _PLURAL: &str = "_PLURAL";

#[derive(Debug, PartialEq)]
pub enum PlurrError {
    EmptyPlaceholder,
    EmptyVariants,
    NotZeroOrPositiveValue,
    UnmatchedOpeningBrace,
    UnmatchedClosingBrace,
    UndefinedParameter,
    UndefinedParameterPairs,
}

type PlurrParams = HashMap<String, String>;

#[derive(Debug)]
pub struct Plurr<'a> {
    locale: &'a str,
    auto_plurals: bool,
    strict: bool,
    params: PlurrParams,
}

impl<'a> Default for Plurr<'a> {
    fn default() -> Self {
        Plurr::new()
    }
}

impl<'a> Plurr<'a> {
    pub fn new() -> Self {
        Plurr {
            locale: "en",
            auto_plurals: true,
            strict: true,
            params: PlurrParams::new(),
        }
    }

    /// Sets the locale for Plurr. If this is not called or an incompatible
    /// `locale_code` is provided, it defaults to English.
    pub fn locale(&mut self, locale_code: &'a str) -> &mut Plurr<'a> {
        self.locale = locale_code;
        self
    }

    /// Toggles the auto-plural behavior.
    /// * With auto-plurals, Plurr will automatically select the plural form for
    /// the current locale.
    /// * Without auto-plurals, you will need to manually provide an `N_PLURAL`
    /// parameter before calling `format()`.
    pub fn auto_plurals(&mut self, auto_plurals: bool) -> &mut Plurr<'a> {
        self.auto_plurals = auto_plurals;
        self
    }

    /// Toggles strict mode. In strict mode, errors are not skipped.
    pub fn strict(&mut self, strict: bool) -> &mut Plurr<'a> {
        self.strict = strict;
        self
    }

    /// Stores a parameter value. Parameters must be fed before calling
    /// `format()`.
    pub fn param(&mut self, key: &str, value: &str) -> &mut Plurr<'a> {
        self.params.insert(key.to_string(), value.to_string());
        self
    }

    /// Formats a Plurr string and returns the evaluated string.
    ///
    /// For the format spec please check out the project
    /// [README](https://github.com/loctools/plurr/blob/master/README.md).
    ///
    /// # Examples
    ///
    /// ```
    /// use plurr::Plurr;
    ///
    /// let mut p = Plurr::new();
    /// let s = "Delete {N_PLURAL:{N} file|{N} files}?";
    ///
    /// assert_eq!(
    ///     p.param("N", "5").format(s),
    ///     Ok("Delete 5 files?".to_string())
    /// );
    /// ```
    pub fn format(&mut self, value: &str) -> Result<String, PlurrError> {
        let mut bracket_count = 0;
        let mut blocks = vec!["".to_string()];

        for chunk in utils::split_string(value, "{", "}") {
            if chunk == "{" {
                bracket_count += 1;
                blocks.push("".to_string());
                continue;
            }

            if chunk == "}" {
                bracket_count -= 1;
                if bracket_count < 0 {
                    return Err(PlurrError::UnmatchedClosingBrace);
                }

                let block = blocks.pop().unwrap();
                let colon_pos_maybe = block.find(':');

                let name = match colon_pos_maybe {
                    Some(colon_pos) => {
                        if self.strict && colon_pos == 0 {
                            return Err(PlurrError::EmptyPlaceholder);
                        }
                        // Multiple choices
                        block[0..colon_pos].to_string()
                    }
                    // Simple placeholder
                    None => block.to_owned(),
                };

                if !self.params.contains_key(&name) {
                    let p_pos = name.find(_PLURAL);
                    if self.auto_plurals
                        && p_pos.is_some()
                        && p_pos.unwrap() == name.len() - _PLURAL.len()
                    {
                        let prefix = name[0..p_pos.unwrap()].to_string();
                        if !self.params.contains_key(&prefix) && self.strict {
                            return Err(PlurrError::UndefinedParameterPairs);
                        }

                        // This is where the actual parameter replacing happens
                        let prefix_value_maybe = self.params.get(&prefix).unwrap();
                        let prefix_value;
                        match prefix_value_maybe.parse::<usize>() {
                            Ok(parsed_value) => prefix_value = parsed_value,
                            Err(_) => {
                                if self.strict {
                                    return Err(PlurrError::NotZeroOrPositiveValue);
                                }
                                prefix_value = 0;
                            }
                        }

                        self.params.insert(
                            name.clone(),
                            plurals::plural_func(&self.locale, prefix_value).to_string(),
                        );
                    } else if self.strict {
                        return Err(PlurrError::UndefinedParameter);
                    }
                }

                let result = match colon_pos_maybe {
                    Some(colon_pos) => {
                        // multiple choices
                        let block_len = block.len();

                        if self.strict && colon_pos == block_len - 1 {
                            return Err(PlurrError::EmptyVariants);
                        }

                        let value = self.params.get(&name).unwrap();
                        let mut choice_idx: usize = value.parse().unwrap_or(0);

                        let content_start = colon_pos + 1;
                        let clean_block = &block[content_start..];

                        let parts: Vec<&str> = clean_block.split('|').collect();
                        if choice_idx >= parts.len() {
                            choice_idx = parts.len() - 1;
                        }
                        parts[choice_idx]
                    }
                    None => self.params.get(&name).unwrap(),
                };

                let index = blocks.len() - 1;
                blocks[index].push_str(result);

                continue;
            }

            let index = blocks.len() - 1;
            blocks[index].push_str(&chunk);
        }

        if bracket_count > 0 {
            return Err(PlurrError::UnmatchedOpeningBrace);
        }

        // Clear params for subsequent calls
        self.params.clear();

        Ok(blocks[0].clone())
    }
}
<commit_msg>Avoid extra string allocation<commit_after>// Copyright (C) 2020 Julen Ruiz Aizpuru, https://github.com/julen

//! # Plurr
//!
//! A library for handling plurals/genders/conditionals.

use std::collections::HashMap;

mod plurals;
mod utils;

const _PLURAL: &str = "_PLURAL";

#[derive(Debug, PartialEq)]
pub enum PlurrError {
    EmptyPlaceholder,
    EmptyVariants,
    NotZeroOrPositiveValue,
    UnmatchedOpeningBrace,
    UnmatchedClosingBrace,
    UndefinedParameter,
    UndefinedParameterPairs,
}

type PlurrParams = HashMap<String, String>;

#[derive(Debug)]
pub struct Plurr<'a> {
    locale: &'a str,
    auto_plurals: bool,
    strict: bool,
    params: PlurrParams,
}

impl<'a> Default for Plurr<'a> {
    fn default() -> Self {
        Plurr::new()
    }
}

impl<'a> Plurr<'a> {
    pub fn new() -> Self {
        Plurr {
            locale: "en",
            auto_plurals: true,
            strict: true,
            params: PlurrParams::new(),
        }
    }

    /// Sets the locale for Plurr. If this is not called or an incompatible
    /// `locale_code` is provided, it defaults to English.
    pub fn locale(&mut self, locale_code: &'a str) -> &mut Plurr<'a> {
        self.locale = locale_code;
        self
    }

    /// Toggles the auto-plural behavior.
    /// * With auto-plurals, Plurr will automatically select the plural form for
    /// the current locale.
    /// * Without auto-plurals, you will need to manually provide an `N_PLURAL`
    /// parameter before calling `format()`.
    pub fn auto_plurals(&mut self, auto_plurals: bool) -> &mut Plurr<'a> {
        self.auto_plurals = auto_plurals;
        self
    }

    /// Toggles strict mode. In strict mode, errors are not skipped.
    pub fn strict(&mut self, strict: bool) -> &mut Plurr<'a> {
        self.strict = strict;
        self
    }

    /// Stores a parameter value. Parameters must be fed before calling
    /// `format()`.
    pub fn param(&mut self, key: &str, value: &str) -> &mut Plurr<'a> {
        self.params.insert(key.to_string(), value.to_string());
        self
    }

    /// Formats a Plurr string and returns the evaluated string.
    ///
    /// For the format spec please check out the project
    /// [README](https://github.com/loctools/plurr/blob/master/README.md).
    ///
    /// # Examples
    ///
    /// ```
    /// use plurr::Plurr;
    ///
    /// let mut p = Plurr::new();
    /// let s = "Delete {N_PLURAL:{N} file|{N} files}?";
    ///
    /// assert_eq!(
    ///     p.param("N", "5").format(s),
    ///     Ok("Delete 5 files?".to_string())
    /// );
    /// ```
    pub fn format(&mut self, value: &str) -> Result<String, PlurrError> {
        let mut bracket_count = 0;
        let mut blocks = vec!["".to_string()];

        for chunk in utils::split_string(value, "{", "}") {
            if chunk == "{" {
                bracket_count += 1;
                blocks.push("".to_string());
                continue;
            }

            if chunk == "}" {
                bracket_count -= 1;
                if bracket_count < 0 {
                    return Err(PlurrError::UnmatchedClosingBrace);
                }

                let block = blocks.pop().unwrap();
                let colon_pos_maybe = block.find(':');

                let name = match colon_pos_maybe {
                    Some(colon_pos) => {
                        if self.strict && colon_pos == 0 {
                            return Err(PlurrError::EmptyPlaceholder);
                        }
                        // Multiple choices
                        &block[0..colon_pos]
                    }
                    // Simple placeholder
                    None => &block,
                };

                if !self.params.contains_key(name) {
                    let p_pos = name.find(_PLURAL);
                    if self.auto_plurals
                        && p_pos.is_some()
                        && p_pos.unwrap() == name.len() - _PLURAL.len()
                    {
                        let prefix = name[0..p_pos.unwrap()].to_string();
                        if !self.params.contains_key(&prefix) && self.strict {
                            return Err(PlurrError::UndefinedParameterPairs);
                        }

                        // This is where the actual parameter replacing happens
                        let prefix_value_maybe = self.params.get(&prefix).unwrap();
                        let prefix_value;
                        match prefix_value_maybe.parse::<usize>() {
                            Ok(parsed_value) => prefix_value = parsed_value,
                            Err(_) => {
                                if self.strict {
                                    return Err(PlurrError::NotZeroOrPositiveValue);
                                }
                                prefix_value = 0;
                            }
                        }

                        self.params.insert(
                            name.to_owned(),
                            plurals::plural_func(&self.locale, prefix_value).to_string(),
                        );
                    } else if self.strict {
                        return Err(PlurrError::UndefinedParameter);
                    }
                }

                let result = match colon_pos_maybe {
                    Some(colon_pos) => {
                        // multiple choices
                        let block_len = block.len();

                        if self.strict && colon_pos == block_len - 1 {
                            return Err(PlurrError::EmptyVariants);
                        }

                        let value = self.params.get(name).unwrap();
                        let mut choice_idx: usize = value.parse().unwrap_or(0);

                        let content_start = colon_pos + 1;
                        let clean_block = &block[content_start..];

                        let parts: Vec<&str> = clean_block.split('|').collect();
                        if choice_idx >= parts.len() {
                            choice_idx = parts.len() - 1;
                        }
                        parts[choice_idx]
                    }
                    None => self.params.get(name).unwrap(),
                };

                let index = blocks.len() - 1;
                blocks[index].push_str(result);

                continue;
            }

            let index = blocks.len() - 1;
            blocks[index].push_str(&chunk);
        }

        if bracket_count > 0 {
            return Err(PlurrError::UnmatchedOpeningBrace);
        }

        // Clear params for subsequent calls
        self.params.clear();

        Ok(blocks[0].clone())
    }
}
<|endoftext|>


<commit_before>//! Manages the sliding windows.  Each time a tick is pushed in, the max/min price of each
//! of the monitored time perdiods is updated.  When ticks become older than the period of
//! the greatest window-newest tick, they are dropped off the structure.
//!
//! NOTE: This algorithm could be more efficiently impelented by a joint linked-list/binary-heap.
//! Traversing a Vector is currently used instead due to the complexity of implementing the
//! alternative.  However, if the latency and inefficniecy caused ever becomes a problem,
//! there are options available.

use std::collections::{HashMap, VecDeque};
use test;

use algobot_util::tick::Tick;

/// Contains the computed information about the ticks in each time monitored time frame.
#[derive(Debug, Copy, Clone)]
pub struct Window {
    pub min_price_t: Option<Tick>,
    pub max_price_t: Option<Tick>,
    pub oldest_t:    Option<(i64, Tick)>
}

impl Window {
    pub fn new_empty() -> Window {
        Window {
            min_price_t: None,
            max_price_t: None,
            oldest_t:   None
        }
    }
}

pub struct WindowManager {
    pub data: VecDeque<Tick>,
    pub windows: HashMap<i64, Window>,
    pub max_period: Option<i64>
}

/// Determines whether a tick is out of range of a window or not
fn out_of_range(period: i64, oldest_tick: Tick, newest_tick: Tick) -> bool {
    newest_tick.timestamp - oldest_tick.timestamp > period
}

impl WindowManager {
    pub fn new() -> WindowManager {
        WindowManager {
            data: VecDeque::new(),
            windows: HashMap::new(),
            max_period: None
        }
    }

    pub fn add_period(&mut self, period: i64) {
        self.windows.insert(period, Window::new_empty());
        // update max period
        match self.max_period {
            Some(_max_period) => {
                if period > _max_period {
                    self.max_period = Some(period);
                }
            },
            None => self.max_period = Some(period)
        }
    }

    pub fn remove_period(&mut self, period: i64) {
        let mut o: Option<i64> = None;
        for (k, _) in self.windows.iter() {
            if *k == period {
                o = Some(*k);
                break;
            }
        }
        if o.is_some() {
            self.windows.remove(&o.unwrap());
            // assume that max_period is already set since we're removing an element
            if self.max_period.unwrap() == period {
                // calculate new max period
                let mut max: Option<i64> = None;
                for (k, _) in self.windows.iter() {
                    if max.is_none() || *k > max.unwrap() {
                        max = Some(*k);
                    }
                }
                self.max_period = max;
            }
        }
    }

    // Processes a new tick and returns a Vec containing periods whose mins/maxes have changed.
    pub fn push(&mut self, t: Tick) -> Vec<(i64, Window)> {
        // data is pushed on the back and popped off the front
        self.data.push_back(t);
        self.trim_overflow(t)
    }

    /// Removes ticks from the front of the queue until all timestamps are within
    /// the maximum period of the WindowManager.  For each tick that is removed,
    /// check to see if it was the maximum/minimum of any windows.  If it was,
    /// update that window.
    fn trim_overflow(&mut self, newest_tick: Tick) -> Vec<(i64, Window)> {
        let mut altered_periods = Vec::new();

        // iterate over all windows and check if the tick is out of their range
        //
        // NOTE: Currently windows are iterated over in an arbitrary order making it impossible to
        // do optimizations like use the max of smaller windows to avoid having to iterate over
        // those parts of larger windows.
        for (&period, &window) in self.windows.iter() {
            let mut window = window;
            // println!("Window before: {:?}", window);
            let mut altered = false;
            // if it's the first tick
            if window.oldest_t.is_none() {
                window.oldest_t = Some((0, newest_tick));
            } else {
                // if our current oldest tick is no longer in range of the window
                if out_of_range(period, window.oldest_t.unwrap().1, newest_tick) {
                    // index of previous oldest tick
                    let mut i = window.oldest_t.unwrap().0 + 1;

                    // loop through ticks until we find one that's inside the window
                    while let Some(newer_tick) = self.data.get(i as usize) {
                        i += 1;
                        if !out_of_range(period, *newer_tick, newest_tick) {
                            window.oldest_t = Some((i, *newer_tick));
                            break;
                        } else {
                            // update the max/min prices if they were in an out-of-range tick
                            if window.max_price_t.is_some() && window.max_price_t.unwrap() == *newer_tick {
                                window.max_price_t = None;
                                altered = true;
                            }
                            if window.min_price_t.is_some() && window.min_price_t.unwrap() == *newer_tick {
                                window.min_price_t = None;
                                altered = true;
                            }
                        }
                    }
                    // at this point the oldest tick for the window is either None or accurate
                    // (not accounting for the possibility of the newest tick)
                }
            }

            if window.min_price_t == None || window.max_price_t == None {
                // there is at least one tick so we can .unwrap()
                let extremes = self.get_extreme_ticks(period).unwrap();
                window.min_price_t = Some(extremes.0);
                window.max_price_t = Some(extremes.1);
                altered = true;
            } else {
                // check if newest_tick is a new max/min tick for the period
                if newest_tick.mid() < window.min_price_t.unwrap().mid() {
                    window.min_price_t = Some(newest_tick);
                    altered = true;
                }
                if newest_tick.mid() > window.max_price_t.unwrap().mid() {
                    window.max_price_t = Some(newest_tick);
                    altered = true;
                }
            }

            if altered {
                // Add the mutated window to the alteration list
                altered_periods.push((period, window));
                // println!("Window after: {:?}", window);
            }
        }

        // make the alterations queued in the loop
        for &(period, window) in altered_periods.iter() {
            self.windows.insert(period, window);
        }

        // drop ticks that are outside of the range of the largest window
        while out_of_range(self.max_period.unwrap(), *self.data.front().unwrap(), newest_tick) {
            self.data.pop_front();
        }

        altered_periods
    }

    /// Gets the max and min tick in a window by iterating over every tick it contains.
    ///
    /// Returns Some(min_tick, max_tick)
    fn get_extreme_ticks(&self, period: i64) -> Option<(Tick, Tick)> {
        let newest_timestamp = self.data.back().unwrap().timestamp;
        let mut max_tick = Tick::null();
        let mut min_tick = Tick::null();
        let mut max_price = 0f64;
        let mut min_price = 0f64;

        for t in self.data.iter() {
            if newest_timestamp - t.timestamp > period {
                break;
            }

            let price = t.mid();
            if price > max_price {
                max_tick = *t;
                max_price = price;
            }

            if price < min_price || min_price == 0f64 {
                min_tick = *t;
                min_price = price;
            }
        }

        if min_price == 0f64 {
            return None
        }

        Some((min_tick, max_tick))
    }
}

#[test]
fn period_addition_removal() {
    let mut wm = WindowManager::new();
    wm.add_period(2);
    wm.add_period(4);
    assert_eq!(wm.windows.len(), 2);
    wm.remove_period(2);
    wm.remove_period(4);
}

#[test]
fn min_max_accuracy() {
    let mut wm = WindowManager::new();
    wm.add_period(5);
    wm.add_period(10);
    println!("result of push: {:?}", wm.push(Tick{timestamp: 1i64, bid: 1f64, ask: 1f64}));
    println!("result of push: {:?}", wm.push(Tick{timestamp: 3i64, bid: 2f64, ask: 2f64}));
    // println!("result of push: {:?}", wm.push(Tick{timestamp: 1i64, bid: 1f64, ask: 1f64}));
    // println!("result of push: {:?}", wm.push(Tick{timestamp: 1i64, bid: 1f64, ask: 1f64}));
    panic!("{:?}", "s");
}

#[bench]
fn large_wm_processing_speed(b: &mut test::Bencher) {
    let mut wm = WindowManager::new();
    wm.add_period(5000000);
    // preload with 500,000 ticks
    for i in 0..5000000 {
        wm.push(Tick{timestamp: i as i64, bid: 1f64, ask: 1f64});
    }
    let mut i = 5000000;
    b.iter(|| {
        i = i + 1;
        wm.push(Tick{timestamp: i as i64, bid: 1f64, ask: 1f64});
    });
}
<commit_msg>Completed main bulk of WindowManager<commit_after>//! Manages the sliding windows.  Each time a tick is pushed in, the max/min price of each
//! of the monitored time perdiods is updated.  When ticks become older than the period of
//! the greatest window-newest tick, they are dropped off the structure.
//!
//! NOTE: This algorithm could be more efficiently impelented by a joint linked-list/binary-heap.
//! Traversing a Vector is currently used instead due to the complexity of implementing the
//! alternative.  However, if the latency and inefficniecy caused ever becomes a problem,
//! there are options available.

use std::collections::{HashMap, VecDeque};
use test;

use algobot_util::tick::Tick;

/// Contains the computed information about the ticks in each time monitored time frame.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Window {
    // TODO: These really don't have to be options.
    pub min_price_t: Option<Tick>,
    pub max_price_t: Option<Tick>,
    pub oldest_t:    Option<Tick>
}

impl Window {
    pub fn new_empty() -> Window {
        Window {
            min_price_t: None,
            max_price_t: None,
            oldest_t:   None
        }
    }
}

pub struct WindowManager {
    pub data: VecDeque<Tick>,
    pub windows: HashMap<i64, Window>,
    pub max_period: Option<i64>
}

/// Determines whether a tick is out of range of a window or not
fn out_of_range(period: i64, old_tick: Tick, newest_tick: Tick) -> bool {
    newest_tick.timestamp - old_tick.timestamp >= period
}

/// Checks if a tick is a max/min tick of a Window
fn is_max_min(t: Tick, window: Window) -> bool {
    if window.min_price_t.is_none() {
        // doesn't matter since we already have a recalc queued
        return false
    }
    t == window.min_price_t.unwrap() || t == window.max_price_t.unwrap()
}

impl WindowManager {
    pub fn new() -> WindowManager {
        WindowManager {
            data: VecDeque::new(),
            windows: HashMap::new(),
            max_period: None
        }
    }

    pub fn add_period(&mut self, period: i64) {
        self.windows.insert(period, Window::new_empty());
        // update max period
        match self.max_period {
            Some(_max_period) => {
                if period > _max_period {
                    self.max_period = Some(period);
                }
            },
            None => self.max_period = Some(period)
        }
    }

    pub fn remove_period(&mut self, period: i64) {
        let mut o: Option<i64> = None;
        for (k, _) in self.windows.iter() {
            if *k == period {
                o = Some(*k);
                break;
            }
        }
        if o.is_some() {
            self.windows.remove(&o.unwrap());
            // assume that max_period is already set since we're removing an element
            if self.max_period.unwrap() == period {
                // calculate new max period
                let mut max: Option<i64> = None;
                for (k, _) in self.windows.iter() {
                    if max.is_none() || *k > max.unwrap() {
                        max = Some(*k);
                    }
                }
                self.max_period = max;
            }
        }
    }

    // Processes a new tick and returns a Vec containing periods whose mins/maxes have changed.
    pub fn push(&mut self, t: Tick) -> HashMap<i64, Window> {
        // data is pushed on the back and popped off the front
        if self.data.len() > 0 {
            assert!(t.timestamp > self.data.back().unwrap().timestamp);
        }
        self.data.push_back(t);

        let mut ret = HashMap::new();
        for (period, window) in self.trim_overflow(t) {
            ret.insert(period, window);
        }

        ret
    }

    /// Removes ticks from the front of the queue until all timestamps are within
    /// the maximum period of the WindowManager.  For each tick that is removed,
    /// check to see if it was the maximum/minimum of any windows.  If it was,
    /// update that window.
    fn trim_overflow(&mut self, newest_tick: Tick) -> Vec<(i64, Window)> {
        let mut altered_periods = Vec::new();

        // iterate over all windows and check if the tick is out of their range
        //
        // NOTE: Currently windows are iterated over in an arbitrary order making it impossible to
        // do optimizations like use the max of smaller windows to avoid having to iterate over
        // those parts of larger windows.
        for (&period, &window) in self.windows.iter() {
            let mut window = window;
            let mut altered = false;
            // if it's the first tick
            if window.oldest_t.is_none() {
                window.oldest_t = Some(newest_tick);
                window.min_price_t = Some(newest_tick);
                window.max_price_t = Some(newest_tick);
                altered = true;
            } else {
                // if our current oldest tick is no longer in range of the window
                if out_of_range(period, window.oldest_t.unwrap(), newest_tick) {
                    // have to re-scan the entire array starting at the oldest
                    let mut i = 0;

                    // loop through ticks until we find one that's inside the window
                    while let Some(newer_tick) = self.data.get(i as usize) {
                        i += 1;
                        if !out_of_range(period, *newer_tick, newest_tick) {
                            // found the first in-range tick
                            window.oldest_t = Some(*newer_tick);
                            if is_max_min(*newer_tick, window) {
                                window.min_price_t = None;
                                window.max_price_t = None;
                            }
                            break;
                        }

                        // If the tick that went out of range max/min needs updating
                        if is_max_min(*newer_tick, window) {
                            window.min_price_t = None;
                            window.max_price_t = None;
                        }
                    }
                    // at this point the oldest tick for the window is either None or accurate
                    // (not accounting for the possibility of the newest tick)
                }
            }

            if window.min_price_t.is_none() || window.max_price_t.is_none() {
                // there is at least one tick so we can .unwrap()
                let extremes = self.get_extreme_ticks(period).unwrap();
                window.min_price_t = Some(extremes.0);
                window.max_price_t = Some(extremes.1);
                altered = true;
            } else {
                // check if newest_tick is a new max/min tick for the period
                if newest_tick.mid() < window.min_price_t.unwrap().mid() {
                    window.min_price_t = Some(newest_tick);
                    altered = true;
                }
                if newest_tick.mid() > window.max_price_t.unwrap().mid() {
                    window.max_price_t = Some(newest_tick);
                    altered = true;
                }
            }

            if altered {
                // Add the mutated window to the alteration list
                altered_periods.push((period, window));
            }
        }

        // make the alterations queued in the loop
        for &(period, window) in altered_periods.iter() {
            self.windows.insert(period, window);
        }

        // drop ticks that are outside of the range of the largest window
        while out_of_range(self.max_period.unwrap(), *self.data.front().unwrap(), newest_tick) {
            self.data.pop_front();
        }

        altered_periods
    }

    /// Gets the max and min tick in a window by iterating over every tick it contains.
    ///
    /// Returns Some(min_tick, max_tick)
    fn get_extreme_ticks(&self, period: i64) -> Option<(Tick, Tick)> {
        let newest_timestamp = self.data.back().unwrap().timestamp;
        let mut max_tick = Tick::null();
        let mut min_tick = Tick::null();
        let mut max_price = 0f64;
        let mut min_price = 0f64;

        // loop over all ticks, newest to oldest
        for t in self.data.iter().rev() {
            if newest_timestamp - t.timestamp >= period {
                break;
            }

            let price = t.mid();
            if price > max_price {
                max_tick = *t;
                max_price = price;
            }

            if price < min_price || min_price == 0f64 {
                min_tick = *t;
                min_price = price;
            }
        }

        if min_price == 0f64 {
            return None
        }

        Some((min_tick, max_tick))
    }
}

#[test]
fn period_addition_removal() {
    let mut wm = WindowManager::new();
    wm.add_period(2);
    wm.add_period(4);
    assert_eq!(wm.windows.len(), 2);
    wm.remove_period(2);
    wm.remove_period(4);
}

#[test]
fn min_max_accuracy() {
    let mut wm = WindowManager::new();
    wm.add_period(2);
    wm.add_period(4);

    let t1 = Tick{timestamp: 1i64, bid: 0.5f64, ask: 0.5f64};
    let wl = wm.push(t1);
    assert_eq!(*wl.get(&2).unwrap(), Window{min_price_t: Some(t1), max_price_t: Some(t1), oldest_t: Some(t1)});
    assert_eq!(*wl.get(&4).unwrap(), Window{min_price_t: Some(t1), max_price_t: Some(t1), oldest_t: Some(t1)});

    let t2 = Tick{timestamp: 2i64, bid: 2f64, ask: 2f64};
    let w2 = wm.push(t2);
    assert_eq!(*w2.get(&2).unwrap(), Window{min_price_t: Some(t1), max_price_t: Some(t2), oldest_t: Some(t1)});
    assert_eq!(*w2.get(&4).unwrap(), Window{min_price_t: Some(t1), max_price_t: Some(t2), oldest_t: Some(t1)});

    let t3 = Tick{timestamp: 3i64, bid: 4f64, ask: 4f64};
    let w3 = wm.push(t3);
    assert_eq!(*w3.get(&2).unwrap(), Window{min_price_t: Some(t2), max_price_t: Some(t3), oldest_t: Some(t2)});
    assert_eq!(*w3.get(&4).unwrap(), Window{min_price_t: Some(t1), max_price_t: Some(t3), oldest_t: Some(t1)});

    let t4 = Tick{timestamp: 6i64, bid: 5f64, ask: 5f64};
    let w4 = wm.push(t4);
    assert_eq!(*w4.get(&2).unwrap(), Window{min_price_t: Some(t4), max_price_t: Some(t4), oldest_t: Some(t4)});
    assert_eq!(*w4.get(&4).unwrap(), Window{min_price_t: Some(t3), max_price_t: Some(t4), oldest_t: Some(t3)});
}

// #[bench]
// fn large_wm_processing_speed(b: &mut test::Bencher) {
//     let mut wm = WindowManager::new();
//     wm.add_period(5000000);
//     // preload with 500,000 ticks
//     for i in 0..5000000 {
//         wm.push(Tick{timestamp: i as i64, bid: 1f64, ask: 1f64});
//     }
//     let mut i = 5000000;
//     b.iter(|| {
//         i = i + 1;
//         wm.push(Tick{timestamp: i as i64, bid: 1f64, ask: 1f64});
//     });
// }
<|endoftext|>


<commit_before>use std::ops::{Mul,Add,Sub};
use num::traits::{Zero,One,Float};

#[derive(PartialEq,Debug)]
pub struct Vec3<T> where T: Mul<T, Output = T> + Add<T, Output = T> + Sub<T, Output = T> + Zero + Copy + Clone {
    pub x: T,
    pub y: T,
    pub z: T,
}

impl <T> Vec3<T> where T : Mul<T, Output = T> + Add<T, Output = T> + Sub<T, Output = T> + Zero + Copy + Clone {
    pub fn new(xx: T, yy: T, zz: T) -> Vec3<T> {
        Vec3 { x: xx, y: yy, z: zz }
    }

    pub fn zero() -> Vec3<T> {
        Vec3::new(T::zero(), T::zero(), T::zero())
    }

    pub fn diagonal(xx: T) -> Vec3<T> {
        Vec3::new(xx, xx, xx)
    }

    pub fn dot(self, rhs: Vec3<T>) -> T {
         self.x * rhs.x + self.y * rhs.y + self.z * rhs.z
    }

    pub fn cross(self, rhs: Vec3<T>) -> Vec3<T> {
        Vec3::new(
            self.y * rhs.z - self.z * rhs.y,
            self.z * rhs.x - self.x * rhs.z,
            self.x * rhs.y - self.y * rhs.x,
            )
    }
}

impl <T> Add<Vec3<T>> for Vec3<T> where T : Mul<T, Output = T> + Add<T, Output = T> + Sub<T, Output = T> + Zero + Copy + Clone {
    type Output = Vec3<T>;

    fn add(self, rhs: Vec3<T>) -> Vec3<T> {
        Vec3::new(
            self.x + rhs.x,
            self.y + rhs.y,
            self.z + rhs.z,
            )
    }
}

impl <T> Sub<Vec3<T>> for Vec3<T> where T : Mul<T, Output = T> + Add<T, Output = T> + Sub<T, Output = T> + Zero + Copy + Clone {
    type Output = Vec3<T>;

    fn sub(self, rhs: Vec3<T>) -> Vec3<T> {
        Vec3::new(
            self.x - rhs.x,
            self.y - rhs.y,
            self.z - rhs.z,
            )
    }
}

impl <T> Mul<T> for Vec3<T> where T : Mul<T, Output = T> + Add<T, Output = T> + Sub<T, Output = T> + Zero + Copy + Clone {
    type Output = Vec3<T>;

    fn mul(self, rhs: T) -> Vec3<T> {
        Vec3::new(
            self.x * rhs,
            self.y * rhs,
            self.z * rhs,
            )
    }
}

impl <T> Vec3<T> where T: Mul<T, Output=T> + One + Zero + Float + Clone + Copy {
    pub fn length(& self) -> T {
        (self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
    }

    pub fn normalize(&mut self) {
        let length: T = self.length();
        if length > T::zero() {
            let inverse_length = T::one() / length;

            self.x = self.x * inverse_length;
            self.y = self.y * inverse_length;
            self.z = self.z * inverse_length;
        }
    }
}

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

    #[test]
    fn should_be_created_with_constructor() {
        let v: Vec3<f64> = Vec3::new(0.0, 1.0, 2.0);

        assert_eq!(v, Vec3 { x: 0.0, y: 1.0, z: 2.0 });
    }

    #[test]
    fn should_initialize_to_zero_vector() {
        let v: Vec3<f64> = Vec3::zero();

        assert_eq!(v, Vec3::<f64>{ x: 0.0, y: 0.0, z: 0.0 });
    }

    #[test]
    fn should_initialize_to_diagonal_vector() {
        let v: Vec3<f64> = Vec3::diagonal(1.0);

        assert_eq!(v, Vec3::<f64>{ x: 1.0, y: 1.0, z: 1.0 });
    }

    #[test]
    fn should_know_dot_product() {
        let u: Vec3<f64> = Vec3::diagonal(1.0);
        let v: Vec3<f64> = Vec3::new(0.0, 1.0, 2.0);

        let dot_product: f64 = u.dot(v);

        assert_eq!(dot_product, 3.0);
    }

    #[test]
    fn should_know_its_length() {
        let v: Vec3<f64> = Vec3 { x: 0.0, y: 0.0, z: 0.0 };

        assert_eq!(v.length(), 0.0);
    }

    #[test]
    fn should_be_able_to_normalize() {
        let mut v: Vec3<f64> = Vec3::new(3.0, 0.0, 0.0);
        v.normalize();

        assert_eq!(v, Vec3 { x: 1.0, y: 0.0, z: 0.0 });
    }

    #[test]
    fn should_know_cross_product() {
        let v: Vec3<f64> = Vec3::new(1.0, 0.0, 0.0);
        let w: Vec3<f64> = Vec3::new(0.0, 1.0, 0.0);

        let product = v.cross(w);

        assert_eq!(product, Vec3 { x: 0.0, y: 0.0, z: 1.0 });
    }

    #[test]
    fn should_form_vector_sum() {
        let v: Vec3<f64> = Vec3::new(1.0, 0.0, 0.0);
        let w: Vec3<f64> = Vec3::new(0.0, 1.0, 0.0);

        let sum = v + w;

        assert_eq!(sum, Vec3 { x: 1.0, y: 1.0, z: 0.0 });
    }

    #[test]
    fn should_form_vector_difference() {
        let v: Vec3<f64> = Vec3::new(1.0, 0.0, 0.0);
        let w: Vec3<f64> = Vec3::new(0.0, 1.0, 0.0);

        let sum = v - w;

        assert_eq!(sum, Vec3 { x: 1.0, y: -1.0, z: 0.0 });
    }

    #[test]
    fn should_form_scalar_multiplication() {
        let v: Vec3<f64> = Vec3::new(1.0, 0.0, 0.0);
        let c: f64 = 2.0;

        let scalar = v * c;

        assert_eq!(scalar, Vec3 { x: 2.0, y: 0.0, z: 0.0 });
    }
}
<commit_msg>Implement sphericalToCartesian<commit_after>use std::ops::{Mul,Add,Sub};
use num::traits::{Zero,One,Float};

#[derive(PartialEq,Debug)]
pub struct Vec3<T> where T: Mul<T, Output = T> + Add<T, Output = T> + Sub<T, Output = T> + Zero + Copy + Clone {
    pub x: T,
    pub y: T,
    pub z: T,
}

impl <T> Vec3<T> where T : Mul<T, Output = T> + Add<T, Output = T> + Sub<T, Output = T> + Zero + Copy + Clone {
    pub fn new(xx: T, yy: T, zz: T) -> Vec3<T> {
        Vec3 { x: xx, y: yy, z: zz }
    }

    pub fn zero() -> Vec3<T> {
        Vec3::new(T::zero(), T::zero(), T::zero())
    }

    pub fn diagonal(xx: T) -> Vec3<T> {
        Vec3::new(xx, xx, xx)
    }

    pub fn dot(self, rhs: Vec3<T>) -> T {
         self.x * rhs.x + self.y * rhs.y + self.z * rhs.z
    }

    pub fn cross(self, rhs: Vec3<T>) -> Vec3<T> {
        Vec3::new(
            self.y * rhs.z - self.z * rhs.y,
            self.z * rhs.x - self.x * rhs.z,
            self.x * rhs.y - self.y * rhs.x,
            )
    }
}

impl <T> Add<Vec3<T>> for Vec3<T> where T : Mul<T, Output = T> + Add<T, Output = T> + Sub<T, Output = T> + Zero + Copy + Clone {
    type Output = Vec3<T>;

    fn add(self, rhs: Vec3<T>) -> Vec3<T> {
        Vec3::new(
            self.x + rhs.x,
            self.y + rhs.y,
            self.z + rhs.z,
            )
    }
}

impl <T> Sub<Vec3<T>> for Vec3<T> where T : Mul<T, Output = T> + Add<T, Output = T> + Sub<T, Output = T> + Zero + Copy + Clone {
    type Output = Vec3<T>;

    fn sub(self, rhs: Vec3<T>) -> Vec3<T> {
        Vec3::new(
            self.x - rhs.x,
            self.y - rhs.y,
            self.z - rhs.z,
            )
    }
}

impl <T> Mul<T> for Vec3<T> where T : Mul<T, Output = T> + Add<T, Output = T> + Sub<T, Output = T> + Zero + Copy + Clone {
    type Output = Vec3<T>;

    fn mul(self, rhs: T) -> Vec3<T> {
        Vec3::new(
            self.x * rhs,
            self.y * rhs,
            self.z * rhs,
            )
    }
}

impl <T> Vec3<T> where T: Mul<T, Output=T> + One + Zero + Float + Clone + Copy {
    #[allow(non_snake_case)]
    pub fn sphericalToCartesian(theta: T, phi: T) -> Vec3<T> {
        Vec3::new(phi.cos() * theta.sin(), phi.sin() * theta.sin(), theta.cos())
    }

    pub fn length(& self) -> T {
        (self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
    }

    pub fn normalize(&mut self) {
        let length: T = self.length();
        if length > T::zero() {
            let inverse_length = T::one() / length;

            self.x = self.x * inverse_length;
            self.y = self.y * inverse_length;
            self.z = self.z * inverse_length;
        }
    }
}

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

    #[test]
    fn should_be_created_with_constructor() {
        let v: Vec3<f64> = Vec3::new(0.0, 1.0, 2.0);

        assert_eq!(v, Vec3 { x: 0.0, y: 1.0, z: 2.0 });
    }

    #[test]
    fn should_initialize_to_zero_vector() {
        let v: Vec3<f64> = Vec3::zero();

        assert_eq!(v, Vec3::<f64>{ x: 0.0, y: 0.0, z: 0.0 });
    }

    #[test]
    fn should_initialize_to_diagonal_vector() {
        let v: Vec3<f64> = Vec3::diagonal(1.0);

        assert_eq!(v, Vec3::<f64>{ x: 1.0, y: 1.0, z: 1.0 });
    }

    #[test]
    fn should_know_dot_product() {
        let u: Vec3<f64> = Vec3::diagonal(1.0);
        let v: Vec3<f64> = Vec3::new(0.0, 1.0, 2.0);

        let dot_product: f64 = u.dot(v);

        assert_eq!(dot_product, 3.0);
    }

    #[test]
    fn should_know_its_length() {
        let v: Vec3<f64> = Vec3 { x: 0.0, y: 0.0, z: 0.0 };

        assert_eq!(v.length(), 0.0);
    }

    #[test]
    fn should_be_able_to_normalize() {
        let mut v: Vec3<f64> = Vec3::new(3.0, 0.0, 0.0);
        v.normalize();

        assert_eq!(v, Vec3 { x: 1.0, y: 0.0, z: 0.0 });
    }

    #[test]
    fn should_know_cross_product() {
        let v: Vec3<f64> = Vec3::new(1.0, 0.0, 0.0);
        let w: Vec3<f64> = Vec3::new(0.0, 1.0, 0.0);

        let product = v.cross(w);

        assert_eq!(product, Vec3 { x: 0.0, y: 0.0, z: 1.0 });
    }

    #[test]
    fn should_form_vector_sum() {
        let v: Vec3<f64> = Vec3::new(1.0, 0.0, 0.0);
        let w: Vec3<f64> = Vec3::new(0.0, 1.0, 0.0);

        let sum = v + w;

        assert_eq!(sum, Vec3 { x: 1.0, y: 1.0, z: 0.0 });
    }

    #[test]
    fn should_form_vector_difference() {
        let v: Vec3<f64> = Vec3::new(1.0, 0.0, 0.0);
        let w: Vec3<f64> = Vec3::new(0.0, 1.0, 0.0);

        let sum = v - w;

        assert_eq!(sum, Vec3 { x: 1.0, y: -1.0, z: 0.0 });
    }

    #[test]
    fn should_form_scalar_multiplication() {
        let v: Vec3<f64> = Vec3::new(1.0, 0.0, 0.0);
        let c: f64 = 2.0;

        let scalar = v * c;

        assert_eq!(scalar, Vec3 { x: 2.0, y: 0.0, z: 0.0 });
    }

    #[test]
    fn should_know_how_to_convert_from_polar_coordinates() {
        let v: Vec3<f64> = Vec3::sphericalToCartesian(0.0, 0.0);

        assert_eq!(v, Vec3 { x: 0.0, y: 0.0, z: 1.0 });
    }
}
<|endoftext|>


<commit_before>use cairo;
use cairo::MatrixTrait;

use aspect_ratio::AspectRatio;
use drawing_ctx;
use drawing_ctx::RsvgDrawingCtx;
use util::*;
use viewbox::*;

#[derive(Debug, Copy, Clone, PartialEq)]
pub enum ClipMode {
    ClipToViewport,
    ClipToVbox,
}

pub fn draw_in_viewport<F>(
    vx: f64,
    vy: f64,
    vw: f64,
    vh: f64,
    clip_mode: ClipMode,
    do_clip: bool,
    vbox: Option<ViewBox>,
    preserve_aspect_ratio: AspectRatio,
    affine: cairo::Matrix,
    draw_ctx: *const RsvgDrawingCtx,
    draw_fn: F,
) where
    F: FnOnce(),
{
    let mut ctx = RsvgDrawingCtxWrapper(draw_ctx);

    in_viewport(
        &mut ctx,
        vx,
        vy,
        vw,
        vh,
        clip_mode,
        do_clip,
        vbox,
        preserve_aspect_ratio,
        affine,
        draw_fn,
    );
}

trait ViewportCtx {
    fn push_view_box(&mut self, width: f64, height: f64);
    fn pop_view_box(&mut self);
    fn push_discrete_layer(&mut self);
    fn pop_discrete_layer(&mut self);
    fn add_clipping_rect(&mut self, x: f64, y: f64, w: f64, h: f64);
    fn set_affine(&mut self, affine: cairo::Matrix);
}

struct RsvgDrawingCtxWrapper(pub *const RsvgDrawingCtx);

impl ViewportCtx for RsvgDrawingCtxWrapper {
    fn push_view_box(&mut self, width: f64, height: f64) {
        drawing_ctx::push_view_box(self.0, width, height);
    }

    fn pop_view_box(&mut self) {
        drawing_ctx::pop_view_box(self.0);
    }

    fn push_discrete_layer(&mut self) {
        drawing_ctx::push_discrete_layer(self.0);
    }

    fn pop_discrete_layer(&mut self) {
        drawing_ctx::pop_discrete_layer(self.0);
    }

    fn add_clipping_rect(&mut self, x: f64, y: f64, w: f64, h: f64) {
        drawing_ctx::add_clipping_rect(self.0, x, y, w, h);
    }

    fn set_affine(&mut self, affine: cairo::Matrix) {
        drawing_ctx::set_current_state_affine(self.0, affine);
    }
}

fn in_viewport<F>(
    ctx: &mut ViewportCtx,
    vx: f64,
    vy: f64,
    vw: f64,
    vh: f64,
    clip_mode: ClipMode,
    do_clip: bool,
    vbox: Option<ViewBox>,
    preserve_aspect_ratio: AspectRatio,
    mut affine: cairo::Matrix,
    draw_fn: F,
) where
    F: FnOnce(),
{
    // width or height set to 0 disables rendering of the element
    // https://www.w3.org/TR/SVG/struct.html#SVGElementWidthAttribute
    // https://www.w3.org/TR/SVG/struct.html#UseElementWidthAttribute
    // https://www.w3.org/TR/SVG/struct.html#ImageElementWidthAttribute
    // https://www.w3.org/TR/SVG/painting.html#MarkerWidthAttribute

    if double_equals(vw, 0.0) || double_equals(vh, 0.0) {
        return;
    }

    let old_affine = affine;

    if let Some(vbox) = vbox {
        // the preserveAspectRatio attribute is only used if viewBox is specified
        // https://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute

        if double_equals(vbox.0.width, 0.0) || double_equals(vbox.0.height, 0.0) {
            // Width or height of 0 for the viewBox disables rendering of the element
            // https://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
            return;
        }

        ctx.push_view_box(vbox.0.width, vbox.0.height);
        ctx.push_discrete_layer();

        let (x, y, w, h) =
            preserve_aspect_ratio.compute(vbox.0.width, vbox.0.height, vx, vy, vw, vh);

        affine.translate(x, y);
        affine.scale(w / vbox.0.width, h / vbox.0.height);
        affine.translate(-vbox.0.x, -vbox.0.y);

        ctx.set_affine(affine);

        if do_clip && clip_mode == ClipMode::ClipToVbox {
            ctx.add_clipping_rect(vbox.0.x, vbox.0.y, vbox.0.width, vbox.0.height);
        }
    } else {
        ctx.push_view_box(vw, vh);
        ctx.push_discrete_layer();

        affine.translate(vx, vy);
        ctx.set_affine(affine);
    }

    if do_clip && clip_mode == ClipMode::ClipToViewport {
        ctx.set_affine(old_affine);
        ctx.add_clipping_rect(vx, vy, vw, vh);
        ctx.set_affine(affine);
    }

    draw_fn();

    ctx.pop_discrete_layer();
    ctx.pop_view_box();
}

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

    #[derive(Default, PartialEq)]
    struct Ctx {
        pub view_box_size: Option<(f64, f64)>,
        pub clipping_rect: Option<(f64, f64, f64, f64)>,
        pub affine: Option<cairo::Matrix>,

        pub expected_view_box_size: Option<(f64, f64)>,
        pub expected_clipping_rect: Option<(f64, f64, f64, f64)>,
        pub expected_affine: Option<cairo::Matrix>,
    }

    impl ViewportCtx for Ctx {
        fn push_view_box(&mut self, width: f64, height: f64) {
            self.view_box_size = Some((width, height));
        }

        fn pop_view_box(&mut self) {}

        fn push_discrete_layer(&mut self) {}

        fn pop_discrete_layer(&mut self) {}

        fn add_clipping_rect(&mut self, x: f64, y: f64, w: f64, h: f64) {
            self.clipping_rect = Some((x, y, w, h));
        }

        fn set_affine(&mut self, affine: cairo::Matrix) {
            self.affine = Some(affine);
        }
    }

    fn call_in_viewport(
        vx: f64,
        vy: f64,
        vw: f64,
        vh: f64,
        clip_mode: ClipMode,
        do_clip: bool,
        vbox: Option<ViewBox>,
        preserve_aspect_ratio: AspectRatio,
        affine: cairo::Matrix,
        ctx: &mut Ctx,
    ) {
        in_viewport(
            ctx,
            vx,
            vy,
            vw,
            vh,
            clip_mode,
            do_clip,
            vbox,
            preserve_aspect_ratio,
            affine,
            || (),
        );

        assert_eq!(ctx.view_box_size, ctx.expected_view_box_size);
        assert_eq!(ctx.clipping_rect, ctx.expected_clipping_rect);
        assert_eq!(ctx.affine, ctx.expected_affine);
    }

    #[test]
    fn clip_to_viewport() {
        let mut affine = cairo::Matrix::identity();
        affine.scale(0.20, 0.20);

        let mut ctx = Ctx {
            view_box_size: None,
            clipping_rect: None,
            affine: None,

            expected_view_box_size: Some((50.0, 50.0)),
            expected_clipping_rect: Some((10.0, 10.0, 10.0, 10.0)),
            expected_affine: Some(affine),
        };

        call_in_viewport(
            10.0,
            10.0,
            10.0,
            10.0,
            ClipMode::ClipToViewport,
            true,
            Some(ViewBox(cairo::Rectangle {
                x: 50.0,
                y: 50.0,
                width: 50.0,
                height: 50.0,
            })),
            AspectRatio::parse("xMidYMid meet", ()).unwrap(),
            cairo::Matrix::identity(),
            &mut ctx,
        );
    }

    #[test]
    fn clip_to_vbox() {
        let mut affine = cairo::Matrix::identity();
        affine.translate(10.0, 10.0);
        affine.scale(0.40, 0.40);

        let mut ctx = Ctx {
            view_box_size: None,
            clipping_rect: None,
            affine: None,

            expected_view_box_size: Some((50.0, 50.0)),
            expected_clipping_rect: Some((0.0, 0.0, 50.0, 50.0)),
            expected_affine: Some(affine),
        };

        call_in_viewport(
            10.0,
            10.0,
            20.0,
            20.0,
            ClipMode::ClipToVbox,
            true,
            Some(ViewBox(cairo::Rectangle {
                x: 0.0,
                y: 0.0,
                width: 50.0,
                height: 50.0,
            })),
            AspectRatio::parse("xMidYMid meet", ()).unwrap(),
            cairo::Matrix::identity(),
            &mut ctx,
        );
    }
}
<commit_msg>viewport.rs: in tests, use numbers that can be represented exactly as f64<commit_after>use cairo;
use cairo::MatrixTrait;

use aspect_ratio::AspectRatio;
use drawing_ctx;
use drawing_ctx::RsvgDrawingCtx;
use util::*;
use viewbox::*;

#[derive(Debug, Copy, Clone, PartialEq)]
pub enum ClipMode {
    ClipToViewport,
    ClipToVbox,
}

pub fn draw_in_viewport<F>(
    vx: f64,
    vy: f64,
    vw: f64,
    vh: f64,
    clip_mode: ClipMode,
    do_clip: bool,
    vbox: Option<ViewBox>,
    preserve_aspect_ratio: AspectRatio,
    affine: cairo::Matrix,
    draw_ctx: *const RsvgDrawingCtx,
    draw_fn: F,
) where
    F: FnOnce(),
{
    let mut ctx = RsvgDrawingCtxWrapper(draw_ctx);

    in_viewport(
        &mut ctx,
        vx,
        vy,
        vw,
        vh,
        clip_mode,
        do_clip,
        vbox,
        preserve_aspect_ratio,
        affine,
        draw_fn,
    );
}

trait ViewportCtx {
    fn push_view_box(&mut self, width: f64, height: f64);
    fn pop_view_box(&mut self);
    fn push_discrete_layer(&mut self);
    fn pop_discrete_layer(&mut self);
    fn add_clipping_rect(&mut self, x: f64, y: f64, w: f64, h: f64);
    fn set_affine(&mut self, affine: cairo::Matrix);
}

struct RsvgDrawingCtxWrapper(pub *const RsvgDrawingCtx);

impl ViewportCtx for RsvgDrawingCtxWrapper {
    fn push_view_box(&mut self, width: f64, height: f64) {
        drawing_ctx::push_view_box(self.0, width, height);
    }

    fn pop_view_box(&mut self) {
        drawing_ctx::pop_view_box(self.0);
    }

    fn push_discrete_layer(&mut self) {
        drawing_ctx::push_discrete_layer(self.0);
    }

    fn pop_discrete_layer(&mut self) {
        drawing_ctx::pop_discrete_layer(self.0);
    }

    fn add_clipping_rect(&mut self, x: f64, y: f64, w: f64, h: f64) {
        drawing_ctx::add_clipping_rect(self.0, x, y, w, h);
    }

    fn set_affine(&mut self, affine: cairo::Matrix) {
        drawing_ctx::set_current_state_affine(self.0, affine);
    }
}

fn in_viewport<F>(
    ctx: &mut ViewportCtx,
    vx: f64,
    vy: f64,
    vw: f64,
    vh: f64,
    clip_mode: ClipMode,
    do_clip: bool,
    vbox: Option<ViewBox>,
    preserve_aspect_ratio: AspectRatio,
    mut affine: cairo::Matrix,
    draw_fn: F,
) where
    F: FnOnce(),
{
    // width or height set to 0 disables rendering of the element
    // https://www.w3.org/TR/SVG/struct.html#SVGElementWidthAttribute
    // https://www.w3.org/TR/SVG/struct.html#UseElementWidthAttribute
    // https://www.w3.org/TR/SVG/struct.html#ImageElementWidthAttribute
    // https://www.w3.org/TR/SVG/painting.html#MarkerWidthAttribute

    if double_equals(vw, 0.0) || double_equals(vh, 0.0) {
        return;
    }

    let old_affine = affine;

    if let Some(vbox) = vbox {
        // the preserveAspectRatio attribute is only used if viewBox is specified
        // https://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute

        if double_equals(vbox.0.width, 0.0) || double_equals(vbox.0.height, 0.0) {
            // Width or height of 0 for the viewBox disables rendering of the element
            // https://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
            return;
        }

        ctx.push_view_box(vbox.0.width, vbox.0.height);
        ctx.push_discrete_layer();

        let (x, y, w, h) =
            preserve_aspect_ratio.compute(vbox.0.width, vbox.0.height, vx, vy, vw, vh);

        affine.translate(x, y);
        affine.scale(w / vbox.0.width, h / vbox.0.height);
        affine.translate(-vbox.0.x, -vbox.0.y);

        ctx.set_affine(affine);

        if do_clip && clip_mode == ClipMode::ClipToVbox {
            ctx.add_clipping_rect(vbox.0.x, vbox.0.y, vbox.0.width, vbox.0.height);
        }
    } else {
        ctx.push_view_box(vw, vh);
        ctx.push_discrete_layer();

        affine.translate(vx, vy);
        ctx.set_affine(affine);
    }

    if do_clip && clip_mode == ClipMode::ClipToViewport {
        ctx.set_affine(old_affine);
        ctx.add_clipping_rect(vx, vy, vw, vh);
        ctx.set_affine(affine);
    }

    draw_fn();

    ctx.pop_discrete_layer();
    ctx.pop_view_box();
}

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

    #[derive(Default, PartialEq)]
    struct Ctx {
        pub view_box_size: Option<(f64, f64)>,
        pub clipping_rect: Option<(f64, f64, f64, f64)>,
        pub affine: Option<cairo::Matrix>,

        pub expected_view_box_size: Option<(f64, f64)>,
        pub expected_clipping_rect: Option<(f64, f64, f64, f64)>,
        pub expected_affine: Option<cairo::Matrix>,
    }

    impl ViewportCtx for Ctx {
        fn push_view_box(&mut self, width: f64, height: f64) {
            self.view_box_size = Some((width, height));
        }

        fn pop_view_box(&mut self) {}

        fn push_discrete_layer(&mut self) {}

        fn pop_discrete_layer(&mut self) {}

        fn add_clipping_rect(&mut self, x: f64, y: f64, w: f64, h: f64) {
            self.clipping_rect = Some((x, y, w, h));
        }

        fn set_affine(&mut self, affine: cairo::Matrix) {
            self.affine = Some(affine);
        }
    }

    fn call_in_viewport(
        vx: f64,
        vy: f64,
        vw: f64,
        vh: f64,
        clip_mode: ClipMode,
        do_clip: bool,
        vbox: Option<ViewBox>,
        preserve_aspect_ratio: AspectRatio,
        affine: cairo::Matrix,
        ctx: &mut Ctx,
    ) {
        in_viewport(
            ctx,
            vx,
            vy,
            vw,
            vh,
            clip_mode,
            do_clip,
            vbox,
            preserve_aspect_ratio,
            affine,
            || (),
        );

        assert_eq!(ctx.view_box_size, ctx.expected_view_box_size);
        assert_eq!(ctx.clipping_rect, ctx.expected_clipping_rect);
        assert_eq!(ctx.affine, ctx.expected_affine);
    }

    #[test]
    fn clip_to_viewport() {
        let mut affine = cairo::Matrix::identity();
        affine.scale(0.25, 0.25);

        let mut ctx = Ctx {
            view_box_size: None,
            clipping_rect: None,
            affine: None,

            expected_view_box_size: Some((40.0, 40.0)),
            expected_clipping_rect: Some((10.0, 10.0, 10.0, 10.0)),
            expected_affine: Some(affine),
        };

        call_in_viewport(
            10.0,
            10.0,
            10.0,
            10.0,
            ClipMode::ClipToViewport,
            true,
            Some(ViewBox(cairo::Rectangle {
                x: 40.0,
                y: 40.0,
                width: 40.0,
                height: 40.0,
            })),
            AspectRatio::parse("xMidYMid meet", ()).unwrap(),
            cairo::Matrix::identity(),
            &mut ctx,
        );
    }

    #[test]
    fn clip_to_vbox() {
        let mut affine = cairo::Matrix::identity();
        affine.translate(10.0, 10.0);
        affine.scale(0.40, 0.40);

        let mut ctx = Ctx {
            view_box_size: None,
            clipping_rect: None,
            affine: None,

            expected_view_box_size: Some((50.0, 50.0)),
            expected_clipping_rect: Some((0.0, 0.0, 50.0, 50.0)),
            expected_affine: Some(affine),
        };

        call_in_viewport(
            10.0,
            10.0,
            20.0,
            20.0,
            ClipMode::ClipToVbox,
            true,
            Some(ViewBox(cairo::Rectangle {
                x: 0.0,
                y: 0.0,
                width: 50.0,
                height: 50.0,
            })),
            AspectRatio::parse("xMidYMid meet", ()).unwrap(),
            cairo::Matrix::identity(),
            &mut ctx,
        );
    }
}
<|endoftext|>