<commit_before>//  * This file is part of the uutils coreutils package.
//  *
//  * (c) Alex Lyon <arcterus@mail.com>
//  *
//  * For the full copyright and license information, please view the LICENSE
//  * file that was distributed with this source code.

// spell-checker:ignore (ToDO) RFILE refsize rfilename fsize tsize

#[macro_use]
extern crate uucore;

use clap::{App, Arg};
use std::fs::{metadata, OpenOptions};
use std::io::ErrorKind;
use std::path::Path;

#[derive(Debug, Eq, PartialEq)]
enum TruncateMode {
    Reference(u64),
    Absolute(u64),
    Extend(u64),
    Reduce(u64),
    AtMost(u64),
    AtLeast(u64),
    RoundDown(u64),
    RoundUp(u64),
}

static ABOUT: &str = "Shrink or extend the size of each file to the specified size.";
static VERSION: &str = env!("CARGO_PKG_VERSION");

pub mod options {
    pub static IO_BLOCKS: &str = "io-blocks";
    pub static NO_CREATE: &str = "no-create";
    pub static REFERENCE: &str = "reference";
    pub static SIZE: &str = "size";
}

static ARG_FILES: &str = "files";

fn get_usage() -> String {
    format!("{0} [OPTION]... [FILE]...", executable!())
}

fn get_long_usage() -> String {
    String::from(
        "
    SIZE is an integer with an optional prefix and optional unit.
    The available units (K, M, G, T, P, E, Z, and Y) use the following format:
        'KB' =>           1000 (kilobytes)
        'K'  =>           1024 (kibibytes)
        'MB' =>      1000*1000 (megabytes)
        'M'  =>      1024*1024 (mebibytes)
        'GB' => 1000*1000*1000 (gigabytes)
        'G'  => 1024*1024*1024 (gibibytes)
    SIZE may also be prefixed by one of the following to adjust the size of each
    file based on its current size:
        '+'  => extend by
        '-'  => reduce by
        '<'  => at most
        '>'  => at least
        '/'  => round down to multiple of
        '%'  => round up to multiple of",
    )
}

pub fn uumain(args: impl uucore::Args) -> i32 {
    let usage = get_usage();
    let long_usage = get_long_usage();

    let matches = App::new(executable!())
        .version(VERSION)
        .about(ABOUT)
        .usage(&usage[..])
        .after_help(&long_usage[..])
        .arg(
            Arg::with_name(options::IO_BLOCKS)
            .short("o")
            .long(options::IO_BLOCKS)
            .help("treat SIZE as the number of I/O blocks of the file rather than bytes (NOT IMPLEMENTED)")
        )
        .arg(
            Arg::with_name(options::NO_CREATE)
            .short("c")
            .long(options::NO_CREATE)
            .help("do not create files that do not exist")
        )
        .arg(
            Arg::with_name(options::REFERENCE)
            .short("r")
            .long(options::REFERENCE)
            .help("base the size of each file on the size of RFILE")
            .value_name("RFILE")
        )
        .arg(
            Arg::with_name(options::SIZE)
            .short("s")
            .long("size")
            .help("set or adjust the size of each file according to SIZE, which is in bytes unless --io-blocks is specified")
            .value_name("SIZE")
        )
        .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true).min_values(1))
        .get_matches_from(args);

    let files: Vec<String> = matches
        .values_of(ARG_FILES)
        .map(|v| v.map(ToString::to_string).collect())
        .unwrap_or_default();

    if files.is_empty() {
        show_error!("Missing an argument");
        return 1;
    } else {
        let io_blocks = matches.is_present(options::IO_BLOCKS);
        let no_create = matches.is_present(options::NO_CREATE);
        let reference = matches.value_of(options::REFERENCE).map(String::from);
        let size = matches.value_of(options::SIZE).map(String::from);
        if reference.is_none() && size.is_none() {
            crash!(1, "you must specify either --reference or --size");
        } else {
            truncate(no_create, io_blocks, reference, size, files);
        }
    }

    0
}

fn truncate(
    no_create: bool,
    _: bool,
    reference: Option<String>,
    size: Option<String>,
    filenames: Vec<String>,
) {
    let mode = match size {
        Some(size_string) => match parse_mode_and_size(&size_string) {
            Ok(m) => m,
            Err(_) => crash!(1, "Invalid number: ‘{}’", size_string),
        },
        None => TruncateMode::Reference(0),
    };

    let refsize = match reference {
        Some(ref rfilename) => {
            match mode {
                // Only Some modes work with a reference
                TruncateMode::Reference(_) => (), //No --size was given
                TruncateMode::Extend(_) => (),
                TruncateMode::Reduce(_) => (),
                _ => crash!(1, "you must specify a relative ‘--size’ with ‘--reference’"),
            };
            match metadata(rfilename) {
                Ok(meta) => meta.len(),
                Err(f) => match f.kind() {
                    ErrorKind::NotFound => {
                        crash!(1, "cannot stat '{}': No such file or directory", rfilename)
                    }
                    _ => crash!(1, "{}", f.to_string()),
                },
            }
        }
        None => 0,
    };
    for filename in &filenames {
        let path = Path::new(filename);
        match OpenOptions::new().write(true).create(!no_create).open(path) {
            Ok(file) => {
                let fsize = match reference {
                    Some(_) => refsize,
                    None => match metadata(filename) {
                        Ok(meta) => meta.len(),
                        Err(f) => {
                            show_warning!("{}", f.to_string());
                            continue;
                        }
                    },
                };
                let tsize: u64 = match mode {
                    TruncateMode::Absolute(modsize) => modsize,
                    TruncateMode::Reference(_) => fsize,
                    TruncateMode::Extend(modsize) => fsize + modsize,
                    TruncateMode::Reduce(modsize) => fsize - modsize,
                    TruncateMode::AtMost(modsize) => fsize.min(modsize),
                    TruncateMode::AtLeast(modsize) => fsize.max(modsize),
                    TruncateMode::RoundDown(modsize) => fsize - fsize % modsize,
                    TruncateMode::RoundUp(modsize) => fsize + fsize % modsize,
                };
                match file.set_len(tsize) {
                    Ok(_) => {}
                    Err(f) => crash!(1, "{}", f.to_string()),
                };
            }
            Err(f) => crash!(1, "{}", f.to_string()),
        }
    }
}

/// Decide whether a character is one of the size modifiers, like '+' or '<'.
fn is_modifier(c: char) -> bool {
    c == '+' || c == '-' || c == '<' || c == '>' || c == '/' || c == '%'
}

/// Parse a size string with optional modifier symbol as its first character.
///
/// A size string is as described in [`parse_size`]. The first character
/// of `size_string` might be a modifier symbol, like `'+'` or
/// `'<'`. The first element of the pair returned by this function
/// indicates which modifier symbol was present, or
/// [`TruncateMode::Absolute`] if none.
///
/// # Panics
///
/// If `size_string` is empty, or if no number could be parsed from the
/// given string (for example, if the string were `"abc"`).
///
/// # Examples
///
/// ```rust,ignore
/// assert_eq!(parse_mode_and_size("+123"), (TruncateMode::Extend, 123));
/// ```
fn parse_mode_and_size(size_string: &str) -> Result<TruncateMode, ()> {
    // Trim any whitespace.
    let size_string = size_string.trim();

    // Get the modifier character from the size string, if any. For
    // example, if the argument is "+123", then the modifier is '+'.
    let c = size_string.chars().next().unwrap();
    let size_string = if is_modifier(c) {
        &size_string[1..]
    } else {
        size_string
    };
    parse_size(size_string).map(match c {
        '+' => TruncateMode::Extend,
        '-' => TruncateMode::Reduce,
        '<' => TruncateMode::AtMost,
        '>' => TruncateMode::AtLeast,
        '/' => TruncateMode::RoundDown,
        '%' => TruncateMode::RoundUp,
        _ => TruncateMode::Absolute,
    })
}

/// Parse a size string into a number of bytes.
///
/// A size string comprises an integer and an optional unit. The unit
/// may be K, M, G, T, P, E, Z, or Y (powers of 1024) or KB, MB,
/// etc. (powers of 1000).
///
/// # Errors
///
/// This function returns an error if the string does not begin with a
/// numeral, or if the unit is not one of the supported units described
/// in the preceding section.
///
/// # Examples
///
/// ```rust,ignore
/// assert_eq!(parse_size("123").unwrap(), 123);
/// assert_eq!(parse_size("123K").unwrap(), 123 * 1024);
/// assert_eq!(parse_size("123KB").unwrap(), 123 * 1000);
/// ```
fn parse_size(size: &str) -> Result<u64, ()> {
    // Get the numeric part of the size argument. For example, if the
    // argument is "123K", then the numeric part is "123".
    let numeric_string: String = size.chars().take_while(|c| c.is_digit(10)).collect();
    let number: u64 = match numeric_string.parse() {
        Ok(n) => n,
        Err(_) => return Err(()),
    };

    // Get the alphabetic units part of the size argument and compute
    // the factor it represents. For example, if the argument is "123K",
    // then the unit part is "K" and the factor is 1024. This may be the
    // empty string, in which case, the factor is 1.
    let n = numeric_string.len();
    let (base, exponent): (u64, u32) = match &size[n..] {
        "" => (1, 0),
        "K" | "k" => (1024, 1),
        "M" | "m" => (1024, 2),
        "G" | "g" => (1024, 3),
        "T" | "t" => (1024, 4),
        "P" | "p" => (1024, 5),
        "E" | "e" => (1024, 6),
        "Z" | "z" => (1024, 7),
        "Y" | "y" => (1024, 8),
        "KB" | "kB" => (1000, 1),
        "MB" | "mB" => (1000, 2),
        "GB" | "gB" => (1000, 3),
        "TB" | "tB" => (1000, 4),
        "PB" | "pB" => (1000, 5),
        "EB" | "eB" => (1000, 6),
        "ZB" | "zB" => (1000, 7),
        "YB" | "yB" => (1000, 8),
        _ => return Err(()),
    };
    let factor = base.pow(exponent);
    Ok(number * factor)
}

#[cfg(test)]
mod tests {
    use crate::parse_mode_and_size;
    use crate::parse_size;
    use crate::TruncateMode;

    #[test]
    fn test_parse_size_zero() {
        assert_eq!(parse_size("0").unwrap(), 0);
        assert_eq!(parse_size("0K").unwrap(), 0);
        assert_eq!(parse_size("0KB").unwrap(), 0);
    }

    #[test]
    fn test_parse_size_without_factor() {
        assert_eq!(parse_size("123").unwrap(), 123);
    }

    #[test]
    fn test_parse_size_kilobytes() {
        assert_eq!(parse_size("123K").unwrap(), 123 * 1024);
        assert_eq!(parse_size("123KB").unwrap(), 123 * 1000);
    }

    #[test]
    fn test_parse_size_megabytes() {
        assert_eq!(parse_size("123").unwrap(), 123);
        assert_eq!(parse_size("123M").unwrap(), 123 * 1024 * 1024);
        assert_eq!(parse_size("123MB").unwrap(), 123 * 1000 * 1000);
    }

    #[test]
    fn test_parse_mode_and_size() {
        assert_eq!(parse_mode_and_size("10"), Ok(TruncateMode::Absolute(10)));
        assert_eq!(parse_mode_and_size("+10"), Ok(TruncateMode::Extend(10)));
        assert_eq!(parse_mode_and_size("-10"), Ok(TruncateMode::Reduce(10)));
        assert_eq!(parse_mode_and_size("<10"), Ok(TruncateMode::AtMost(10)));
        assert_eq!(parse_mode_and_size(">10"), Ok(TruncateMode::AtLeast(10)));
        assert_eq!(parse_mode_and_size("/10"), Ok(TruncateMode::RoundDown(10)));
        assert_eq!(parse_mode_and_size("%10"), Ok(TruncateMode::RoundUp(10)));
    }
}
<commit_msg>truncate: create TruncateMode::to_size() method<commit_after>//  * This file is part of the uutils coreutils package.
//  *
//  * (c) Alex Lyon <arcterus@mail.com>
//  *
//  * For the full copyright and license information, please view the LICENSE
//  * file that was distributed with this source code.

// spell-checker:ignore (ToDO) RFILE refsize rfilename fsize tsize

#[macro_use]
extern crate uucore;

use clap::{App, Arg};
use std::fs::{metadata, OpenOptions};
use std::io::ErrorKind;
use std::path::Path;

#[derive(Debug, Eq, PartialEq)]
enum TruncateMode {
    Reference(u64),
    Absolute(u64),
    Extend(u64),
    Reduce(u64),
    AtMost(u64),
    AtLeast(u64),
    RoundDown(u64),
    RoundUp(u64),
}

impl TruncateMode {
    /// Compute a target size in bytes for this truncate mode.
    ///
    /// `fsize` is the size of the reference file, in bytes.
    ///
    /// # Examples
    ///
    /// ```rust,ignore
    /// let mode = TruncateMode::Extend(5);
    /// let fsize = 10;
    /// assert_eq!(mode.to_size(fsize), 15);
    /// ```
    fn to_size(&self, fsize: u64) -> u64 {
        match self {
            TruncateMode::Absolute(modsize) => *modsize,
            TruncateMode::Reference(_) => fsize,
            TruncateMode::Extend(modsize) => fsize + modsize,
            TruncateMode::Reduce(modsize) => fsize - modsize,
            TruncateMode::AtMost(modsize) => fsize.min(*modsize),
            TruncateMode::AtLeast(modsize) => fsize.max(*modsize),
            TruncateMode::RoundDown(modsize) => fsize - fsize % modsize,
            TruncateMode::RoundUp(modsize) => fsize + fsize % modsize,
        }
    }
}

static ABOUT: &str = "Shrink or extend the size of each file to the specified size.";
static VERSION: &str = env!("CARGO_PKG_VERSION");

pub mod options {
    pub static IO_BLOCKS: &str = "io-blocks";
    pub static NO_CREATE: &str = "no-create";
    pub static REFERENCE: &str = "reference";
    pub static SIZE: &str = "size";
}

static ARG_FILES: &str = "files";

fn get_usage() -> String {
    format!("{0} [OPTION]... [FILE]...", executable!())
}

fn get_long_usage() -> String {
    String::from(
        "
    SIZE is an integer with an optional prefix and optional unit.
    The available units (K, M, G, T, P, E, Z, and Y) use the following format:
        'KB' =>           1000 (kilobytes)
        'K'  =>           1024 (kibibytes)
        'MB' =>      1000*1000 (megabytes)
        'M'  =>      1024*1024 (mebibytes)
        'GB' => 1000*1000*1000 (gigabytes)
        'G'  => 1024*1024*1024 (gibibytes)
    SIZE may also be prefixed by one of the following to adjust the size of each
    file based on its current size:
        '+'  => extend by
        '-'  => reduce by
        '<'  => at most
        '>'  => at least
        '/'  => round down to multiple of
        '%'  => round up to multiple of",
    )
}

pub fn uumain(args: impl uucore::Args) -> i32 {
    let usage = get_usage();
    let long_usage = get_long_usage();

    let matches = App::new(executable!())
        .version(VERSION)
        .about(ABOUT)
        .usage(&usage[..])
        .after_help(&long_usage[..])
        .arg(
            Arg::with_name(options::IO_BLOCKS)
            .short("o")
            .long(options::IO_BLOCKS)
            .help("treat SIZE as the number of I/O blocks of the file rather than bytes (NOT IMPLEMENTED)")
        )
        .arg(
            Arg::with_name(options::NO_CREATE)
            .short("c")
            .long(options::NO_CREATE)
            .help("do not create files that do not exist")
        )
        .arg(
            Arg::with_name(options::REFERENCE)
            .short("r")
            .long(options::REFERENCE)
            .help("base the size of each file on the size of RFILE")
            .value_name("RFILE")
        )
        .arg(
            Arg::with_name(options::SIZE)
            .short("s")
            .long("size")
            .help("set or adjust the size of each file according to SIZE, which is in bytes unless --io-blocks is specified")
            .value_name("SIZE")
        )
        .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true).min_values(1))
        .get_matches_from(args);

    let files: Vec<String> = matches
        .values_of(ARG_FILES)
        .map(|v| v.map(ToString::to_string).collect())
        .unwrap_or_default();

    if files.is_empty() {
        show_error!("Missing an argument");
        return 1;
    } else {
        let io_blocks = matches.is_present(options::IO_BLOCKS);
        let no_create = matches.is_present(options::NO_CREATE);
        let reference = matches.value_of(options::REFERENCE).map(String::from);
        let size = matches.value_of(options::SIZE).map(String::from);
        if reference.is_none() && size.is_none() {
            crash!(1, "you must specify either --reference or --size");
        } else {
            truncate(no_create, io_blocks, reference, size, files);
        }
    }

    0
}

fn truncate(
    no_create: bool,
    _: bool,
    reference: Option<String>,
    size: Option<String>,
    filenames: Vec<String>,
) {
    let mode = match size {
        Some(size_string) => match parse_mode_and_size(&size_string) {
            Ok(m) => m,
            Err(_) => crash!(1, "Invalid number: ‘{}’", size_string),
        },
        None => TruncateMode::Reference(0),
    };

    let refsize = match reference {
        Some(ref rfilename) => {
            match mode {
                // Only Some modes work with a reference
                TruncateMode::Reference(_) => (), //No --size was given
                TruncateMode::Extend(_) => (),
                TruncateMode::Reduce(_) => (),
                _ => crash!(1, "you must specify a relative ‘--size’ with ‘--reference’"),
            };
            match metadata(rfilename) {
                Ok(meta) => meta.len(),
                Err(f) => match f.kind() {
                    ErrorKind::NotFound => {
                        crash!(1, "cannot stat '{}': No such file or directory", rfilename)
                    }
                    _ => crash!(1, "{}", f.to_string()),
                },
            }
        }
        None => 0,
    };
    for filename in &filenames {
        let path = Path::new(filename);
        match OpenOptions::new().write(true).create(!no_create).open(path) {
            Ok(file) => {
                let fsize = match reference {
                    Some(_) => refsize,
                    None => match metadata(filename) {
                        Ok(meta) => meta.len(),
                        Err(f) => {
                            show_warning!("{}", f.to_string());
                            continue;
                        }
                    },
                };
                let tsize = mode.to_size(fsize);
                match file.set_len(tsize) {
                    Ok(_) => {}
                    Err(f) => crash!(1, "{}", f.to_string()),
                };
            }
            Err(f) => crash!(1, "{}", f.to_string()),
        }
    }
}

/// Decide whether a character is one of the size modifiers, like '+' or '<'.
fn is_modifier(c: char) -> bool {
    c == '+' || c == '-' || c == '<' || c == '>' || c == '/' || c == '%'
}

/// Parse a size string with optional modifier symbol as its first character.
///
/// A size string is as described in [`parse_size`]. The first character
/// of `size_string` might be a modifier symbol, like `'+'` or
/// `'<'`. The first element of the pair returned by this function
/// indicates which modifier symbol was present, or
/// [`TruncateMode::Absolute`] if none.
///
/// # Panics
///
/// If `size_string` is empty, or if no number could be parsed from the
/// given string (for example, if the string were `"abc"`).
///
/// # Examples
///
/// ```rust,ignore
/// assert_eq!(parse_mode_and_size("+123"), (TruncateMode::Extend, 123));
/// ```
fn parse_mode_and_size(size_string: &str) -> Result<TruncateMode, ()> {
    // Trim any whitespace.
    let size_string = size_string.trim();

    // Get the modifier character from the size string, if any. For
    // example, if the argument is "+123", then the modifier is '+'.
    let c = size_string.chars().next().unwrap();
    let size_string = if is_modifier(c) {
        &size_string[1..]
    } else {
        size_string
    };
    parse_size(size_string).map(match c {
        '+' => TruncateMode::Extend,
        '-' => TruncateMode::Reduce,
        '<' => TruncateMode::AtMost,
        '>' => TruncateMode::AtLeast,
        '/' => TruncateMode::RoundDown,
        '%' => TruncateMode::RoundUp,
        _ => TruncateMode::Absolute,
    })
}

/// Parse a size string into a number of bytes.
///
/// A size string comprises an integer and an optional unit. The unit
/// may be K, M, G, T, P, E, Z, or Y (powers of 1024) or KB, MB,
/// etc. (powers of 1000).
///
/// # Errors
///
/// This function returns an error if the string does not begin with a
/// numeral, or if the unit is not one of the supported units described
/// in the preceding section.
///
/// # Examples
///
/// ```rust,ignore
/// assert_eq!(parse_size("123").unwrap(), 123);
/// assert_eq!(parse_size("123K").unwrap(), 123 * 1024);
/// assert_eq!(parse_size("123KB").unwrap(), 123 * 1000);
/// ```
fn parse_size(size: &str) -> Result<u64, ()> {
    // Get the numeric part of the size argument. For example, if the
    // argument is "123K", then the numeric part is "123".
    let numeric_string: String = size.chars().take_while(|c| c.is_digit(10)).collect();
    let number: u64 = match numeric_string.parse() {
        Ok(n) => n,
        Err(_) => return Err(()),
    };

    // Get the alphabetic units part of the size argument and compute
    // the factor it represents. For example, if the argument is "123K",
    // then the unit part is "K" and the factor is 1024. This may be the
    // empty string, in which case, the factor is 1.
    let n = numeric_string.len();
    let (base, exponent): (u64, u32) = match &size[n..] {
        "" => (1, 0),
        "K" | "k" => (1024, 1),
        "M" | "m" => (1024, 2),
        "G" | "g" => (1024, 3),
        "T" | "t" => (1024, 4),
        "P" | "p" => (1024, 5),
        "E" | "e" => (1024, 6),
        "Z" | "z" => (1024, 7),
        "Y" | "y" => (1024, 8),
        "KB" | "kB" => (1000, 1),
        "MB" | "mB" => (1000, 2),
        "GB" | "gB" => (1000, 3),
        "TB" | "tB" => (1000, 4),
        "PB" | "pB" => (1000, 5),
        "EB" | "eB" => (1000, 6),
        "ZB" | "zB" => (1000, 7),
        "YB" | "yB" => (1000, 8),
        _ => return Err(()),
    };
    let factor = base.pow(exponent);
    Ok(number * factor)
}

#[cfg(test)]
mod tests {
    use crate::parse_mode_and_size;
    use crate::parse_size;
    use crate::TruncateMode;

    #[test]
    fn test_parse_size_zero() {
        assert_eq!(parse_size("0").unwrap(), 0);
        assert_eq!(parse_size("0K").unwrap(), 0);
        assert_eq!(parse_size("0KB").unwrap(), 0);
    }

    #[test]
    fn test_parse_size_without_factor() {
        assert_eq!(parse_size("123").unwrap(), 123);
    }

    #[test]
    fn test_parse_size_kilobytes() {
        assert_eq!(parse_size("123K").unwrap(), 123 * 1024);
        assert_eq!(parse_size("123KB").unwrap(), 123 * 1000);
    }

    #[test]
    fn test_parse_size_megabytes() {
        assert_eq!(parse_size("123").unwrap(), 123);
        assert_eq!(parse_size("123M").unwrap(), 123 * 1024 * 1024);
        assert_eq!(parse_size("123MB").unwrap(), 123 * 1000 * 1000);
    }

    #[test]
    fn test_parse_mode_and_size() {
        assert_eq!(parse_mode_and_size("10"), Ok(TruncateMode::Absolute(10)));
        assert_eq!(parse_mode_and_size("+10"), Ok(TruncateMode::Extend(10)));
        assert_eq!(parse_mode_and_size("-10"), Ok(TruncateMode::Reduce(10)));
        assert_eq!(parse_mode_and_size("<10"), Ok(TruncateMode::AtMost(10)));
        assert_eq!(parse_mode_and_size(">10"), Ok(TruncateMode::AtLeast(10)));
        assert_eq!(parse_mode_and_size("/10"), Ok(TruncateMode::RoundDown(10)));
        assert_eq!(parse_mode_and_size("%10"), Ok(TruncateMode::RoundUp(10)));
    }
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use extra::url;
use extra::url::Url;
use std::hashmap::HashMap;
use std::os;

/**
Create a URL object from a string. Does various helpful browsery things like

* If there's no current url and the path looks like a file then it will
  create a file url based of the current working directory
* If there's a current url and the new path is relative then the new url
  is based off the current url

*/
// TODO: about:failure->
pub fn parse_url(str_url: &str, base_url: Option<Url>) -> Url {
    let str_url = str_url.trim_chars(& &[' ', '\t', '\n', '\r', '\x0C']).to_owned();
    let schm = url::get_scheme(str_url);
    let str_url = match schm {
        Err(_) => {
            if base_url.is_none() {
                // Assume we've been given a file path. If it's absolute just return
                // it, otherwise make it absolute with the cwd.
                if str_url.starts_with("/") {
                    ~"file://" + str_url
                } else {
                    let mut path = os::getcwd();
                    path.push(str_url);
                    // FIXME (#1094): not the right way to transform a path
                    ~"file://" + path.display().to_str()
                }
            } else {
                let base_url = base_url.unwrap();
                debug!("parse_url: base_url: {:?}", base_url);

                let mut new_url = base_url.clone();
                new_url.query = ~[];
                new_url.fragment = None;

                if str_url.starts_with("//") {
                    new_url.scheme + ":" + str_url
                } else if base_url.path.is_empty() || str_url.starts_with("/") {
                    new_url.path = ~"/";
                    new_url.to_str() + str_url.trim_left_chars(&'/')
                } else if str_url.starts_with("#") {
                    new_url.to_str() + str_url
                } else { // relative path
                    let base_path = base_url.path.trim_right_chars(&|c: char| c != '/');
                    new_url.path = base_path.to_owned();
                    new_url.to_str() + str_url
                }
            }
        },
        Ok((scheme, page)) => {
            match scheme.as_slice() {
                "about" => {
                    match page.as_slice() {
                        "failure" => {
                            let mut path = os::getcwd();
                            path.push("../src/test/html/failure.html");
                            // FIXME (#1094): not the right way to transform a path
                            ~"file://" + path.display().to_str()
                        }
                        // TODO: handle the rest of the about: pages
                        _ => str_url
                    }
                },
                "data" => {
                    // Drop whitespace within data: URLs, e.g. newlines within a base64
                    // src="..." block.  Whitespace intended as content should be
                    // %-encoded or base64'd.
                    str_url.chars().filter(|&c| !c.is_whitespace()).collect()
                },
                _ => str_url
            }
        }
    };

    // FIXME: Need to handle errors
    url::from_str(str_url).unwrap()
}

#[cfg(test)]
mod parse_url_tests {
    use super::parse_url;
    use std::os;

    #[test]
    fn should_create_absolute_file_url_if_base_url_is_none_and_str_url_looks_filey() {
        let file = "local.html";
        let url = parse_url(file, None);
        debug!("url: {:?}", url);
        assert!(url.scheme == ~"file");
        let path = os::getcwd();
        // FIXME (#1094): not the right way to transform a path
        assert!(url.path.contains(path.display().to_str()));
    }

    #[test]
    fn should_create_url_based_on_old_url_1() {
        let old_str = "http://example.com";
        let old_url = parse_url(old_str, None);
        let new_str = "index.html";
        let new_url = parse_url(new_str, Some(old_url));
        assert!(new_url.scheme == ~"http");
        assert!(new_url.host == ~"example.com");
        assert!(new_url.path == ~"/index.html");
    }

    #[test]
    fn should_create_url_based_on_old_url_2() {
        let old_str = "http://example.com/";
        let old_url = parse_url(old_str, None);
        let new_str = "index.html";
        let new_url = parse_url(new_str, Some(old_url));
        assert!(new_url.scheme == ~"http");
        assert!(new_url.host == ~"example.com");
        assert!(new_url.path == ~"/index.html");
    }

    #[test]
    fn should_create_url_based_on_old_url_3() {
        let old_str = "http://example.com/index.html";
        let old_url = parse_url(old_str, None);
        let new_str = "crumpet.html";
        let new_url = parse_url(new_str, Some(old_url));
        assert!(new_url.scheme == ~"http");
        assert!(new_url.host == ~"example.com");
        assert!(new_url.path == ~"/crumpet.html");
    }

    #[test]
    fn should_create_url_based_on_old_url_4() {
        let old_str = "http://example.com/snarf/index.html";
        let old_url = parse_url(old_str, None);
        let new_str = "crumpet.html";
        let new_url = parse_url(new_str, Some(old_url));
        assert!(new_url.scheme == ~"http");
        assert!(new_url.host == ~"example.com");
        assert!(new_url.path == ~"/snarf/crumpet.html");
    }

    #[test]
    fn should_create_url_based_on_old_url_5() {
        let old_str = "http://example.com/index.html";
        let old_url = parse_url(old_str, None);
        let new_str = "#top";
        let new_url = parse_url(new_str, Some(old_url));

        assert!(new_url.scheme == ~"http");
        assert!(new_url.host == ~"example.com");
        assert!(new_url.path == ~"/index.html");
        assert!(new_url.fragment == Some(~"top"));
    }

    #[test]
    fn should_create_url_based_on_old_url_6() {
        use extra::url::UserInfo;

        let old_str = "http://foo:bar@example.com:8080/index.html";
        let old_url = parse_url(old_str, None);
        let new_str = "#top";
        let new_url = parse_url(new_str, Some(old_url));

        assert!(new_url.scheme == ~"http");
        assert!(new_url.user == Some(UserInfo { user: ~"foo", pass: Some(~"bar") }));
        assert!(new_url.host == ~"example.com");
        assert!(new_url.port == Some(~"8080"));
        assert!(new_url.path == ~"/index.html");
        assert!(new_url.fragment == Some(~"top"));
    }

    #[test]
    fn should_create_url_based_on_old_url_7() {
        let old_str = "https://example.com/snarf/index.html";
        let old_url = parse_url(old_str, None);
        let new_str = "//example.com/crumpet.html";
        let new_url = parse_url(new_str, Some(old_url));
        assert!(new_url.scheme == ~"https");
        assert!(new_url.host == ~"example.com");
        assert!(new_url.path == ~"/crumpet.html");
    }

}

pub type UrlMap<T> = HashMap<Url, T>;

pub fn url_map<T: Clone + 'static>() -> UrlMap<T> {
    HashMap::new()
}

<commit_msg>Provide an error message for URL parse failure<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use extra::url;
use extra::url::Url;
use std::hashmap::HashMap;
use std::os;

/**
Create a URL object from a string. Does various helpful browsery things like

* If there's no current url and the path looks like a file then it will
  create a file url based of the current working directory
* If there's a current url and the new path is relative then the new url
  is based off the current url

*/
// TODO: about:failure->
pub fn parse_url(str_url: &str, base_url: Option<Url>) -> Url {
    let str_url = str_url.trim_chars(& &[' ', '\t', '\n', '\r', '\x0C']).to_owned();
    let schm = url::get_scheme(str_url);
    let str_url = match schm {
        Err(_) => {
            if base_url.is_none() {
                // Assume we've been given a file path. If it's absolute just return
                // it, otherwise make it absolute with the cwd.
                if str_url.starts_with("/") {
                    ~"file://" + str_url
                } else {
                    let mut path = os::getcwd();
                    path.push(str_url);
                    // FIXME (#1094): not the right way to transform a path
                    ~"file://" + path.display().to_str()
                }
            } else {
                let base_url = base_url.unwrap();
                debug!("parse_url: base_url: {:?}", base_url);

                let mut new_url = base_url.clone();
                new_url.query = ~[];
                new_url.fragment = None;

                if str_url.starts_with("//") {
                    new_url.scheme + ":" + str_url
                } else if base_url.path.is_empty() || str_url.starts_with("/") {
                    new_url.path = ~"/";
                    new_url.to_str() + str_url.trim_left_chars(&'/')
                } else if str_url.starts_with("#") {
                    new_url.to_str() + str_url
                } else { // relative path
                    let base_path = base_url.path.trim_right_chars(&|c: char| c != '/');
                    new_url.path = base_path.to_owned();
                    new_url.to_str() + str_url
                }
            }
        },
        Ok((scheme, page)) => {
            match scheme.as_slice() {
                "about" => {
                    match page.as_slice() {
                        "failure" => {
                            let mut path = os::getcwd();
                            path.push("../src/test/html/failure.html");
                            // FIXME (#1094): not the right way to transform a path
                            ~"file://" + path.display().to_str()
                        }
                        // TODO: handle the rest of the about: pages
                        _ => str_url
                    }
                },
                "data" => {
                    // Drop whitespace within data: URLs, e.g. newlines within a base64
                    // src="..." block.  Whitespace intended as content should be
                    // %-encoded or base64'd.
                    str_url.chars().filter(|&c| !c.is_whitespace()).collect()
                },
                _ => str_url
            }
        }
    };

    // FIXME: Need to handle errors
    url::from_str(str_url).ok().expect("URL parsing failed")
}

#[cfg(test)]
mod parse_url_tests {
    use super::parse_url;
    use std::os;

    #[test]
    fn should_create_absolute_file_url_if_base_url_is_none_and_str_url_looks_filey() {
        let file = "local.html";
        let url = parse_url(file, None);
        debug!("url: {:?}", url);
        assert!(url.scheme == ~"file");
        let path = os::getcwd();
        // FIXME (#1094): not the right way to transform a path
        assert!(url.path.contains(path.display().to_str()));
    }

    #[test]
    fn should_create_url_based_on_old_url_1() {
        let old_str = "http://example.com";
        let old_url = parse_url(old_str, None);
        let new_str = "index.html";
        let new_url = parse_url(new_str, Some(old_url));
        assert!(new_url.scheme == ~"http");
        assert!(new_url.host == ~"example.com");
        assert!(new_url.path == ~"/index.html");
    }

    #[test]
    fn should_create_url_based_on_old_url_2() {
        let old_str = "http://example.com/";
        let old_url = parse_url(old_str, None);
        let new_str = "index.html";
        let new_url = parse_url(new_str, Some(old_url));
        assert!(new_url.scheme == ~"http");
        assert!(new_url.host == ~"example.com");
        assert!(new_url.path == ~"/index.html");
    }

    #[test]
    fn should_create_url_based_on_old_url_3() {
        let old_str = "http://example.com/index.html";
        let old_url = parse_url(old_str, None);
        let new_str = "crumpet.html";
        let new_url = parse_url(new_str, Some(old_url));
        assert!(new_url.scheme == ~"http");
        assert!(new_url.host == ~"example.com");
        assert!(new_url.path == ~"/crumpet.html");
    }

    #[test]
    fn should_create_url_based_on_old_url_4() {
        let old_str = "http://example.com/snarf/index.html";
        let old_url = parse_url(old_str, None);
        let new_str = "crumpet.html";
        let new_url = parse_url(new_str, Some(old_url));
        assert!(new_url.scheme == ~"http");
        assert!(new_url.host == ~"example.com");
        assert!(new_url.path == ~"/snarf/crumpet.html");
    }

    #[test]
    fn should_create_url_based_on_old_url_5() {
        let old_str = "http://example.com/index.html";
        let old_url = parse_url(old_str, None);
        let new_str = "#top";
        let new_url = parse_url(new_str, Some(old_url));

        assert!(new_url.scheme == ~"http");
        assert!(new_url.host == ~"example.com");
        assert!(new_url.path == ~"/index.html");
        assert!(new_url.fragment == Some(~"top"));
    }

    #[test]
    fn should_create_url_based_on_old_url_6() {
        use extra::url::UserInfo;

        let old_str = "http://foo:bar@example.com:8080/index.html";
        let old_url = parse_url(old_str, None);
        let new_str = "#top";
        let new_url = parse_url(new_str, Some(old_url));

        assert!(new_url.scheme == ~"http");
        assert!(new_url.user == Some(UserInfo { user: ~"foo", pass: Some(~"bar") }));
        assert!(new_url.host == ~"example.com");
        assert!(new_url.port == Some(~"8080"));
        assert!(new_url.path == ~"/index.html");
        assert!(new_url.fragment == Some(~"top"));
    }

    #[test]
    fn should_create_url_based_on_old_url_7() {
        let old_str = "https://example.com/snarf/index.html";
        let old_url = parse_url(old_str, None);
        let new_str = "//example.com/crumpet.html";
        let new_url = parse_url(new_str, Some(old_url));
        assert!(new_url.scheme == ~"https");
        assert!(new_url.host == ~"example.com");
        assert!(new_url.path == ~"/crumpet.html");
    }

}

pub type UrlMap<T> = HashMap<Url, T>;

pub fn url_map<T: Clone + 'static>() -> UrlMap<T> {
    HashMap::new()
}

<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use app_units::Au;
use core_graphics::base::{kCGImageAlphaNoneSkipFirst, kCGImageAlphaPremultipliedLast};
use core_graphics::base::kCGBitmapByteOrder32Little;
use core_graphics::color_space::CGColorSpace;
use core_graphics::context::{CGContext, CGTextDrawingMode};
use core_graphics::data_provider::CGDataProvider;
use core_graphics::font::{CGFont, CGGlyph};
use core_graphics::geometry::{CGPoint, CGSize, CGRect};
use core_text::font::CTFont;
use core_text::font_descriptor::kCTFontDefaultOrientation;
use core_text;
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use webrender_traits::{ColorU, FontKey, FontRenderMode, GlyphDimensions};
use webrender_traits::{GlyphKey, GlyphOptions, SubpixelPoint};
use gamma_lut::{GammaLut, Color as ColorLut};

pub struct FontContext {
    cg_fonts: HashMap<FontKey, CGFont>,
    ct_fonts: HashMap<(FontKey, Au), CTFont>,
    gamma_lut: GammaLut,
}

pub struct RasterizedGlyph {
    pub width: u32,
    pub height: u32,
    pub bytes: Vec<u8>,
}

impl RasterizedGlyph {
    pub fn blank() -> RasterizedGlyph {
        RasterizedGlyph {
            width: 0,
            height: 0,
            bytes: vec![],
        }
    }
}

struct GlyphMetrics {
    rasterized_left: i32,
    rasterized_descent: i32,
    rasterized_ascent: i32,
    rasterized_width: u32,
    rasterized_height: u32,
}

// According to the Skia source code, there's no public API to
// determine if subpixel AA is supported. So jrmuizel ported
// this function from Skia which is used to check if a glyph
// can be rendered with subpixel AA.
fn supports_subpixel_aa() -> bool {
    let mut cg_context = CGContext::create_bitmap_context(1, 1, 8, 4,
                                                          &CGColorSpace::create_device_rgb(),
                                                          kCGImageAlphaNoneSkipFirst |
                                                          kCGBitmapByteOrder32Little);
    let ct_font = core_text::font::new_from_name("Helvetica", 16.).unwrap();
    cg_context.set_should_smooth_fonts(true);
    cg_context.set_should_antialias(true);
    cg_context.set_allows_font_smoothing(true);
    cg_context.set_rgb_fill_color(1.0, 1.0, 1.0, 1.0);
    let point = CGPoint {x: -1., y: 0.};
    let glyph = '|' as CGGlyph;
    ct_font.draw_glyphs(&[glyph], &[point], cg_context.clone());
    let data = cg_context.data();
    data[0] != data[1] || data[1] != data[2]
}

fn get_glyph_metrics(ct_font: &CTFont,
                     glyph: CGGlyph,
                     subpixel_point: &SubpixelPoint) -> GlyphMetrics {
    let bounds = ct_font.get_bounding_rects_for_glyphs(kCTFontDefaultOrientation, &[glyph]);

    let (x_offset, y_offset) = subpixel_point.to_f64();

    // First round out to pixel boundaries
    // CG Origin is bottom left
    let mut left = bounds.origin.x.floor() as i32;
    let mut bottom = bounds.origin.y.floor() as i32;
    let mut right = (bounds.origin.x
                    + bounds.size.width
                    + x_offset).ceil() as i32;
    let mut top = (bounds.origin.y
                  + bounds.size.height
                  + y_offset).ceil() as i32;

    // Expand the bounds by 1 pixel, to give CG room for anti-aliasing.
    // Note that this outset is to allow room for LCD smoothed glyphs. However, the correct outset
    // is not currently known, as CG dilates the outlines by some percentage.
    // This is taken from Skia.
    left -= 1;
    bottom -= 1;
    right += 1;
    top += 1;

    let width = right - left;
    let height = top - bottom;

    let metrics = GlyphMetrics {
        rasterized_left: left,
        rasterized_width: width as u32,
        rasterized_height: height as u32,
        rasterized_ascent: top,
        rasterized_descent: -bottom,
    };

    metrics
}

impl FontContext {
    pub fn new() -> FontContext {
        debug!("Test for subpixel AA support: {}", supports_subpixel_aa());

        // Force CG to use sRGB color space to gamma correct.
        let contrast = 0.0;
        let gamma = 0.0;

        FontContext {
            cg_fonts: HashMap::new(),
            ct_fonts: HashMap::new(),
            gamma_lut: GammaLut::new(contrast, gamma, gamma),
        }
    }

    pub fn add_raw_font(&mut self, font_key: &FontKey, bytes: &[u8]) {
        if self.cg_fonts.contains_key(font_key) {
            return
        }

        let data_provider = CGDataProvider::from_buffer(bytes);
        let cg_font = match CGFont::from_data_provider(data_provider) {
            Err(_) => return,
            Ok(cg_font) => cg_font,
        };
        self.cg_fonts.insert((*font_key).clone(), cg_font);
    }

    pub fn add_native_font(&mut self, font_key: &FontKey, native_font_handle: CGFont) {
        if self.cg_fonts.contains_key(font_key) {
            return
        }

        self.cg_fonts.insert((*font_key).clone(), native_font_handle);
    }

    pub fn delete_font(&mut self, font_key: &FontKey) {
        if let Some(cg_font) = self.cg_fonts.remove(font_key) {
            // Unstable Rust has a retain() method on HashMap that will
            // let us do this in-place. https://github.com/rust-lang/rust/issues/36648
            let ct_font_keys = self.ct_fonts.keys()
                                            .filter(|k| k.0 == *font_key)
                                            .cloned()
                                            .collect::<Vec<_>>();
            for ct_font_key in ct_font_keys {
                self.ct_fonts.remove(&ct_font_key);
            }
        }
    }

    fn get_ct_font(&mut self,
                   font_key: FontKey,
                   size: Au) -> Option<CTFont> {
        match self.ct_fonts.entry(((font_key).clone(), size)) {
            Entry::Occupied(entry) => Some((*entry.get()).clone()),
            Entry::Vacant(entry) => {
                let cg_font = match self.cg_fonts.get(&font_key) {
                    None => return None,
                    Some(cg_font) => cg_font,
                };
                let ct_font = core_text::font::new_from_CGFont(
                        cg_font,
                        size.to_f64_px());
                entry.insert(ct_font.clone());
                Some(ct_font)
            }
        }
    }

    pub fn get_glyph_dimensions(&mut self,
                                key: &GlyphKey) -> Option<GlyphDimensions> {
        self.get_ct_font(key.font_key, key.size).and_then(|ref ct_font| {
            let glyph = key.index as CGGlyph;
            let metrics = get_glyph_metrics(ct_font, glyph, &key.subpixel_point);
            if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 {
                None
            } else {
                Some(GlyphDimensions {
                    left: metrics.rasterized_left,
                    top: metrics.rasterized_ascent,
                    width: metrics.rasterized_width as u32,
                    height: metrics.rasterized_height as u32,
                })
            }
        })
    }

    // Assumes the pixels here are linear values from CG
    fn gamma_correct_pixels(&self, pixels: &mut Vec<u8>, width: usize,
                            height: usize, render_mode: FontRenderMode,
                            color: ColorU) {
        // Then convert back to gamma corrected values.
        let color_lut = ColorLut::new(color.r,
                                     color.g,
                                     color.b,
                                     color.a);
        match render_mode {
            FontRenderMode::Alpha => {
                self.gamma_lut.preblend_grayscale_bgra(pixels, width,
                                                       height, color_lut);
            },
            FontRenderMode::Subpixel => {
                self.gamma_lut.preblend_bgra(pixels, width, height, color_lut);
            },
            _ => {} // Again, give mono untouched since only the alpha matters.
        }
    }

    #[allow(dead_code)]
    fn print_glyph_data(&mut self, data: &Vec<u8>, width: usize, height: usize) {
        // Rust doesn't have step_by support on stable :(
        println!("Width is: {:?} height: {:?}", width, height);
        for i in 0..height {
            let current_height = i * width * 4;

            for pixel in data[current_height .. current_height + (width * 4)].chunks(4) {
                let b = pixel[0];
                let g = pixel[1];
                let r = pixel[2];
                let a = pixel[3];
                print!("({}, {}, {}, {}) ", r, g, b, a);
            }
            println!("");
        }
    }

    pub fn rasterize_glyph(&mut self,
                           key: &GlyphKey,
                           render_mode: FontRenderMode,
                           _glyph_options: Option<GlyphOptions>)
                           -> Option<RasterizedGlyph> {
        match self.get_ct_font(key.font_key, key.size) {
            Some(ref ct_font) => {
                let glyph = key.index as CGGlyph;
                let metrics = get_glyph_metrics(ct_font, glyph, &key.subpixel_point);
                if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 {
                    return Some(RasterizedGlyph::blank())
                }

                let context_flags = match render_mode {
                    FontRenderMode::Subpixel => kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst,
                    FontRenderMode::Alpha | FontRenderMode::Mono => kCGImageAlphaPremultipliedLast,
                };

                let mut cg_context = CGContext::create_bitmap_context(metrics.rasterized_width as usize,
                                                                      metrics.rasterized_height as usize,
                                                                      8,
                                                                      metrics.rasterized_width as usize * 4,
                                                                      &CGColorSpace::create_device_rgb(),
                                                                      context_flags);

                // Tested on mac OS Sierra, 10.12
                // For Mono + alpha, the only values that matter are the alpha values.
                // For subpixel, we need each individual rgb channel.
                // CG has two individual glyphs for subpixel AA (pre-10.11, this is not true):
                // 1) black text on white opaque background
                // 2) white text on black opaque background
                // Gecko does (1). Note, the BG must be opaque for subpixel AA to work.
                // See https://bugzilla.mozilla.org/show_bug.cgi?id=1230366#c35
                //
                // For grayscale / mono, CG still produces two glyphs, but it doesn't matter
                // 1) black text on transparent white - only alpha values filled
                // 2) white text on transparent black - channels == alpha
                //
                // If we draw grayscale/mono on an opaque background
                // the RGB channels are the alpha values from transparent backgrounds
                // with the alpha set as opaque.
                // At the end of all this, WR expects individual RGB channels and ignores alpha
                // for subpixel AA.
                // For alpha/mono, WR ignores all channels other than alpha.
                // Also note that WR expects text to be black bg with white text, so invert
                // when we draw the glyphs.
                let (antialias, smooth) = match render_mode {
                    FontRenderMode::Subpixel => (true, true),
                    FontRenderMode::Alpha => (true, false),
                    FontRenderMode::Mono => (false, false),
                };

                // These are always true in Gecko, even for non-AA fonts
                cg_context.set_allows_font_subpixel_positioning(true);
                cg_context.set_should_subpixel_position_fonts(true);

                // Don't quantize because we're doing it already.
                cg_context.set_allows_font_subpixel_quantization(false);
                cg_context.set_should_subpixel_quantize_fonts(false);

                cg_context.set_allows_font_smoothing(smooth);
                cg_context.set_should_smooth_fonts(smooth);
                cg_context.set_allows_antialiasing(antialias);
                cg_context.set_should_antialias(antialias);

                let (x_offset, y_offset) = key.subpixel_point.to_f64();

                // CG Origin is bottom left, WR is top left. Need -y offset
                let rasterization_origin = CGPoint {
                    x: -metrics.rasterized_left as f64 + x_offset,
                    y: metrics.rasterized_descent as f64 - y_offset,
                };

                // Always draw black text on a white background
                // Fill the background
                cg_context.set_rgb_fill_color(1.0, 1.0, 1.0, 1.0);
                let rect = CGRect {
                    origin: CGPoint {
                        x: 0.0,
                        y: 0.0,
                    },
                    size: CGSize {
                        width: metrics.rasterized_width as f64,
                        height: metrics.rasterized_height as f64,
                    }
                };
                cg_context.fill_rect(rect);

                // Set the text color
                cg_context.set_rgb_fill_color(0.0, 0.0, 0.0, 1.0);
                cg_context.set_text_drawing_mode(CGTextDrawingMode::CGTextFill);
                ct_font.draw_glyphs(&[glyph], &[rasterization_origin], cg_context.clone());

                let mut rasterized_pixels = cg_context.data().to_vec();

                // Convert to linear space for subpixel AA.
                // We explicitly do not do this for grayscale AA
                if render_mode == FontRenderMode::Subpixel {
                    self.gamma_lut.coregraphics_convert_to_linear_bgra(&mut rasterized_pixels,
                                                                       metrics.rasterized_width as usize,
                                                                       metrics.rasterized_height as usize);
                }

                // We need to invert the pixels back since right now
                // transparent pixels are actually opaque white.
                for i in 0..metrics.rasterized_height {
                    let current_height = (i * metrics.rasterized_width * 4) as usize;
                    let end_row = current_height + (metrics.rasterized_width as usize * 4);

                    for mut pixel in rasterized_pixels[current_height .. end_row].chunks_mut(4) {
                        pixel[0] = 255 - pixel[0];
                        pixel[1] = 255 - pixel[1];
                        pixel[2] = 255 - pixel[2];

                        pixel[3] = match render_mode {
                            FontRenderMode::Subpixel => 255,
                            _ => {
                                assert_eq!(pixel[0], pixel[1]);
                                assert_eq!(pixel[0], pixel[2]);
                                pixel[0]
                            }
                        }; // end match
                    } // end row
                } // end height

                self.gamma_correct_pixels(&mut rasterized_pixels,
                                          metrics.rasterized_width as usize,
                                          metrics.rasterized_height as usize,
                                          render_mode,
                                          key.color);

                Some(RasterizedGlyph {
                    width: metrics.rasterized_width,
                    height: metrics.rasterized_height,
                    bytes: rasterized_pixels,
                })
            }
            None => {
                return Some(RasterizedGlyph::blank());
            }
        }
    }
}

<commit_msg>Auto merge of #1063 - waywardmonkeys:fix-cgfont-warning, r=kvark<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use app_units::Au;
use core_graphics::base::{kCGImageAlphaNoneSkipFirst, kCGImageAlphaPremultipliedLast};
use core_graphics::base::kCGBitmapByteOrder32Little;
use core_graphics::color_space::CGColorSpace;
use core_graphics::context::{CGContext, CGTextDrawingMode};
use core_graphics::data_provider::CGDataProvider;
use core_graphics::font::{CGFont, CGGlyph};
use core_graphics::geometry::{CGPoint, CGSize, CGRect};
use core_text::font::CTFont;
use core_text::font_descriptor::kCTFontDefaultOrientation;
use core_text;
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use webrender_traits::{ColorU, FontKey, FontRenderMode, GlyphDimensions};
use webrender_traits::{GlyphKey, GlyphOptions, SubpixelPoint};
use gamma_lut::{GammaLut, Color as ColorLut};

pub struct FontContext {
    cg_fonts: HashMap<FontKey, CGFont>,
    ct_fonts: HashMap<(FontKey, Au), CTFont>,
    gamma_lut: GammaLut,
}

pub struct RasterizedGlyph {
    pub width: u32,
    pub height: u32,
    pub bytes: Vec<u8>,
}

impl RasterizedGlyph {
    pub fn blank() -> RasterizedGlyph {
        RasterizedGlyph {
            width: 0,
            height: 0,
            bytes: vec![],
        }
    }
}

struct GlyphMetrics {
    rasterized_left: i32,
    rasterized_descent: i32,
    rasterized_ascent: i32,
    rasterized_width: u32,
    rasterized_height: u32,
}

// According to the Skia source code, there's no public API to
// determine if subpixel AA is supported. So jrmuizel ported
// this function from Skia which is used to check if a glyph
// can be rendered with subpixel AA.
fn supports_subpixel_aa() -> bool {
    let mut cg_context = CGContext::create_bitmap_context(1, 1, 8, 4,
                                                          &CGColorSpace::create_device_rgb(),
                                                          kCGImageAlphaNoneSkipFirst |
                                                          kCGBitmapByteOrder32Little);
    let ct_font = core_text::font::new_from_name("Helvetica", 16.).unwrap();
    cg_context.set_should_smooth_fonts(true);
    cg_context.set_should_antialias(true);
    cg_context.set_allows_font_smoothing(true);
    cg_context.set_rgb_fill_color(1.0, 1.0, 1.0, 1.0);
    let point = CGPoint {x: -1., y: 0.};
    let glyph = '|' as CGGlyph;
    ct_font.draw_glyphs(&[glyph], &[point], cg_context.clone());
    let data = cg_context.data();
    data[0] != data[1] || data[1] != data[2]
}

fn get_glyph_metrics(ct_font: &CTFont,
                     glyph: CGGlyph,
                     subpixel_point: &SubpixelPoint) -> GlyphMetrics {
    let bounds = ct_font.get_bounding_rects_for_glyphs(kCTFontDefaultOrientation, &[glyph]);

    let (x_offset, y_offset) = subpixel_point.to_f64();

    // First round out to pixel boundaries
    // CG Origin is bottom left
    let mut left = bounds.origin.x.floor() as i32;
    let mut bottom = bounds.origin.y.floor() as i32;
    let mut right = (bounds.origin.x
                    + bounds.size.width
                    + x_offset).ceil() as i32;
    let mut top = (bounds.origin.y
                  + bounds.size.height
                  + y_offset).ceil() as i32;

    // Expand the bounds by 1 pixel, to give CG room for anti-aliasing.
    // Note that this outset is to allow room for LCD smoothed glyphs. However, the correct outset
    // is not currently known, as CG dilates the outlines by some percentage.
    // This is taken from Skia.
    left -= 1;
    bottom -= 1;
    right += 1;
    top += 1;

    let width = right - left;
    let height = top - bottom;

    let metrics = GlyphMetrics {
        rasterized_left: left,
        rasterized_width: width as u32,
        rasterized_height: height as u32,
        rasterized_ascent: top,
        rasterized_descent: -bottom,
    };

    metrics
}

impl FontContext {
    pub fn new() -> FontContext {
        debug!("Test for subpixel AA support: {}", supports_subpixel_aa());

        // Force CG to use sRGB color space to gamma correct.
        let contrast = 0.0;
        let gamma = 0.0;

        FontContext {
            cg_fonts: HashMap::new(),
            ct_fonts: HashMap::new(),
            gamma_lut: GammaLut::new(contrast, gamma, gamma),
        }
    }

    pub fn add_raw_font(&mut self, font_key: &FontKey, bytes: &[u8]) {
        if self.cg_fonts.contains_key(font_key) {
            return
        }

        let data_provider = CGDataProvider::from_buffer(bytes);
        let cg_font = match CGFont::from_data_provider(data_provider) {
            Err(_) => return,
            Ok(cg_font) => cg_font,
        };
        self.cg_fonts.insert((*font_key).clone(), cg_font);
    }

    pub fn add_native_font(&mut self, font_key: &FontKey, native_font_handle: CGFont) {
        if self.cg_fonts.contains_key(font_key) {
            return
        }

        self.cg_fonts.insert((*font_key).clone(), native_font_handle);
    }

    pub fn delete_font(&mut self, font_key: &FontKey) {
        if let Some(_) = self.cg_fonts.remove(font_key) {
            // Unstable Rust has a retain() method on HashMap that will
            // let us do this in-place. https://github.com/rust-lang/rust/issues/36648
            let ct_font_keys = self.ct_fonts.keys()
                                            .filter(|k| k.0 == *font_key)
                                            .cloned()
                                            .collect::<Vec<_>>();
            for ct_font_key in ct_font_keys {
                self.ct_fonts.remove(&ct_font_key);
            }
        }
    }

    fn get_ct_font(&mut self,
                   font_key: FontKey,
                   size: Au) -> Option<CTFont> {
        match self.ct_fonts.entry(((font_key).clone(), size)) {
            Entry::Occupied(entry) => Some((*entry.get()).clone()),
            Entry::Vacant(entry) => {
                let cg_font = match self.cg_fonts.get(&font_key) {
                    None => return None,
                    Some(cg_font) => cg_font,
                };
                let ct_font = core_text::font::new_from_CGFont(
                        cg_font,
                        size.to_f64_px());
                entry.insert(ct_font.clone());
                Some(ct_font)
            }
        }
    }

    pub fn get_glyph_dimensions(&mut self,
                                key: &GlyphKey) -> Option<GlyphDimensions> {
        self.get_ct_font(key.font_key, key.size).and_then(|ref ct_font| {
            let glyph = key.index as CGGlyph;
            let metrics = get_glyph_metrics(ct_font, glyph, &key.subpixel_point);
            if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 {
                None
            } else {
                Some(GlyphDimensions {
                    left: metrics.rasterized_left,
                    top: metrics.rasterized_ascent,
                    width: metrics.rasterized_width as u32,
                    height: metrics.rasterized_height as u32,
                })
            }
        })
    }

    // Assumes the pixels here are linear values from CG
    fn gamma_correct_pixels(&self, pixels: &mut Vec<u8>, width: usize,
                            height: usize, render_mode: FontRenderMode,
                            color: ColorU) {
        // Then convert back to gamma corrected values.
        let color_lut = ColorLut::new(color.r,
                                     color.g,
                                     color.b,
                                     color.a);
        match render_mode {
            FontRenderMode::Alpha => {
                self.gamma_lut.preblend_grayscale_bgra(pixels, width,
                                                       height, color_lut);
            },
            FontRenderMode::Subpixel => {
                self.gamma_lut.preblend_bgra(pixels, width, height, color_lut);
            },
            _ => {} // Again, give mono untouched since only the alpha matters.
        }
    }

    #[allow(dead_code)]
    fn print_glyph_data(&mut self, data: &Vec<u8>, width: usize, height: usize) {
        // Rust doesn't have step_by support on stable :(
        println!("Width is: {:?} height: {:?}", width, height);
        for i in 0..height {
            let current_height = i * width * 4;

            for pixel in data[current_height .. current_height + (width * 4)].chunks(4) {
                let b = pixel[0];
                let g = pixel[1];
                let r = pixel[2];
                let a = pixel[3];
                print!("({}, {}, {}, {}) ", r, g, b, a);
            }
            println!("");
        }
    }

    pub fn rasterize_glyph(&mut self,
                           key: &GlyphKey,
                           render_mode: FontRenderMode,
                           _glyph_options: Option<GlyphOptions>)
                           -> Option<RasterizedGlyph> {
        match self.get_ct_font(key.font_key, key.size) {
            Some(ref ct_font) => {
                let glyph = key.index as CGGlyph;
                let metrics = get_glyph_metrics(ct_font, glyph, &key.subpixel_point);
                if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 {
                    return Some(RasterizedGlyph::blank())
                }

                let context_flags = match render_mode {
                    FontRenderMode::Subpixel => kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst,
                    FontRenderMode::Alpha | FontRenderMode::Mono => kCGImageAlphaPremultipliedLast,
                };

                let mut cg_context = CGContext::create_bitmap_context(metrics.rasterized_width as usize,
                                                                      metrics.rasterized_height as usize,
                                                                      8,
                                                                      metrics.rasterized_width as usize * 4,
                                                                      &CGColorSpace::create_device_rgb(),
                                                                      context_flags);

                // Tested on mac OS Sierra, 10.12
                // For Mono + alpha, the only values that matter are the alpha values.
                // For subpixel, we need each individual rgb channel.
                // CG has two individual glyphs for subpixel AA (pre-10.11, this is not true):
                // 1) black text on white opaque background
                // 2) white text on black opaque background
                // Gecko does (1). Note, the BG must be opaque for subpixel AA to work.
                // See https://bugzilla.mozilla.org/show_bug.cgi?id=1230366#c35
                //
                // For grayscale / mono, CG still produces two glyphs, but it doesn't matter
                // 1) black text on transparent white - only alpha values filled
                // 2) white text on transparent black - channels == alpha
                //
                // If we draw grayscale/mono on an opaque background
                // the RGB channels are the alpha values from transparent backgrounds
                // with the alpha set as opaque.
                // At the end of all this, WR expects individual RGB channels and ignores alpha
                // for subpixel AA.
                // For alpha/mono, WR ignores all channels other than alpha.
                // Also note that WR expects text to be black bg with white text, so invert
                // when we draw the glyphs.
                let (antialias, smooth) = match render_mode {
                    FontRenderMode::Subpixel => (true, true),
                    FontRenderMode::Alpha => (true, false),
                    FontRenderMode::Mono => (false, false),
                };

                // These are always true in Gecko, even for non-AA fonts
                cg_context.set_allows_font_subpixel_positioning(true);
                cg_context.set_should_subpixel_position_fonts(true);

                // Don't quantize because we're doing it already.
                cg_context.set_allows_font_subpixel_quantization(false);
                cg_context.set_should_subpixel_quantize_fonts(false);

                cg_context.set_allows_font_smoothing(smooth);
                cg_context.set_should_smooth_fonts(smooth);
                cg_context.set_allows_antialiasing(antialias);
                cg_context.set_should_antialias(antialias);

                let (x_offset, y_offset) = key.subpixel_point.to_f64();

                // CG Origin is bottom left, WR is top left. Need -y offset
                let rasterization_origin = CGPoint {
                    x: -metrics.rasterized_left as f64 + x_offset,
                    y: metrics.rasterized_descent as f64 - y_offset,
                };

                // Always draw black text on a white background
                // Fill the background
                cg_context.set_rgb_fill_color(1.0, 1.0, 1.0, 1.0);
                let rect = CGRect {
                    origin: CGPoint {
                        x: 0.0,
                        y: 0.0,
                    },
                    size: CGSize {
                        width: metrics.rasterized_width as f64,
                        height: metrics.rasterized_height as f64,
                    }
                };
                cg_context.fill_rect(rect);

                // Set the text color
                cg_context.set_rgb_fill_color(0.0, 0.0, 0.0, 1.0);
                cg_context.set_text_drawing_mode(CGTextDrawingMode::CGTextFill);
                ct_font.draw_glyphs(&[glyph], &[rasterization_origin], cg_context.clone());

                let mut rasterized_pixels = cg_context.data().to_vec();

                // Convert to linear space for subpixel AA.
                // We explicitly do not do this for grayscale AA
                if render_mode == FontRenderMode::Subpixel {
                    self.gamma_lut.coregraphics_convert_to_linear_bgra(&mut rasterized_pixels,
                                                                       metrics.rasterized_width as usize,
                                                                       metrics.rasterized_height as usize);
                }

                // We need to invert the pixels back since right now
                // transparent pixels are actually opaque white.
                for i in 0..metrics.rasterized_height {
                    let current_height = (i * metrics.rasterized_width * 4) as usize;
                    let end_row = current_height + (metrics.rasterized_width as usize * 4);

                    for mut pixel in rasterized_pixels[current_height .. end_row].chunks_mut(4) {
                        pixel[0] = 255 - pixel[0];
                        pixel[1] = 255 - pixel[1];
                        pixel[2] = 255 - pixel[2];

                        pixel[3] = match render_mode {
                            FontRenderMode::Subpixel => 255,
                            _ => {
                                assert_eq!(pixel[0], pixel[1]);
                                assert_eq!(pixel[0], pixel[2]);
                                pixel[0]
                            }
                        }; // end match
                    } // end row
                } // end height

                self.gamma_correct_pixels(&mut rasterized_pixels,
                                          metrics.rasterized_width as usize,
                                          metrics.rasterized_height as usize,
                                          render_mode,
                                          key.color);

                Some(RasterizedGlyph {
                    width: metrics.rasterized_width,
                    height: metrics.rasterized_height,
                    bytes: rasterized_pixels,
                })
            }
            None => {
                return Some(RasterizedGlyph::blank());
            }
        }
    }
}

<|endoftext|>


<commit_before>use std::ops::Range;
pub const STATE_SIZE: usize = 624;

// The following code is a direct translation of the pseudocode found on Wikipedia

/*
 // Create a length 624 array to store the state of the generator
 int[0..623] MT
 int index = 0
*/

pub struct MersenneTwister {
    mt: [u32; STATE_SIZE],
    index: usize,
}

impl MersenneTwister {
    /*
     // Initialize the generator from a seed
     function initialize_generator(int seed) {
         index := 0
         MT[0] := seed
         for i from 1 to 623 { // loop over each element
             MT[i] := lowest 32 bits of
                (1812433253 * (MT[i-1] xor (right shift by 30 bits(MT[i-1]))) + i) // 0x6c078965
         }
     }
    */

    pub fn initialize(seed: u32) -> Self {
        let mut mt = [0; STATE_SIZE];
        mt[0] = seed;
        for i in 1..STATE_SIZE {
            mt[i] = (mt[i - 1] ^ (mt[i - 1] >> 30))
                .wrapping_mul(0x6c07_8965)
                .wrapping_add(i as u32);
        }
        MersenneTwister { mt, index: 0 }
    }

    /*
     // Generate an array of 624 untempered numbers
     function generate_numbers() {
         for i from 0 to 623 {
             int y := (MT[i] and 0x80000000)           // bit 31 (32nd bit) of MT[i]
                + (MT[(i+1) mod 624] and 0x7fffffff)   // bits 0-30 (first 31 bits) of MT[...]
             MT[i] := MT[(i + 397) mod 624] xor (right shift by 1 bit(y))
             if (y mod 2) != 0 { // y is odd
                 MT[i] := MT[i] xor (2567483615) // 0x9908b0df
             }
         }
     }
    */

    fn generate(&mut self) {
        for i in 0..STATE_SIZE {
            let mt = &mut self.mt;
            let y = (mt[i] & 0x8000_0000) | (mt[(i + 1) % STATE_SIZE] & 0x7fff_ffff);
            mt[i] = mt[(i + 397) % STATE_SIZE] ^ (y >> 1);
            if y % 2 != 0 {
                mt[i] ^= 0x9908_b0df;
            }
        }
    }

    pub fn initialize_with_state(mt: [u32; STATE_SIZE]) -> MersenneTwister {
        MersenneTwister { mt: mt, index: 0 }
    }
}

impl Iterator for MersenneTwister {
    type Item = u32;
    /*
     // Extract a tempered pseudorandom number based on the index-th value,
     // calling generate_numbers() every 624 numbers
     function extract_number() {
         if index == 0 {
             generate_numbers()
         }

         int y := MT[index]
         y := y xor (right shift by 11 bits(y))
         y := y xor (left shift by 7 bits(y) and (2636928640)) // 0x9d2c5680
         y := y xor (left shift by 15 bits(y) and (4022730752)) // 0xefc60000
         y := y xor (right shift by 18 bits(y))

         index := (index + 1) mod 624
         return y
     }

    */

    fn next(&mut self) -> Option<u32> {
        if self.index == 0 {
            self.generate();
        }
        let y = temper(self.mt[self.index]);
        self.index = (self.index + 1) % STATE_SIZE;
        Some(y)
    }
}

fn temper(mut y: u32) -> u32 {
    y ^= y >> 11;
    y ^= (y << 7) & 0x9d2c_5680;
    y ^= (y << 15) & 0xefc6_0000;
    y ^= y >> 18;
    y
}

fn inv_rs(mut u: u32, k: u32) -> u32 {
    assert!(k >= 1);
    let mut v = u;
    //Would profit from range_inclusive and std::u32::BITS
    for _ in 0..32 / k + 1 {
        u >>= k;
        v ^= u;
    }
    v
}

fn inv_lsa(u: u32, k: u32, c: u32) -> u32 {
    assert!(k >= 1);
    let mut v = u;
    //Would profit from std::u32::BITS
    for _ in 0..32 / k {
        v = u ^ (v << k & c);
    }
    v
}

pub fn untemper(u: u32) -> u32 {
    inv_rs(
        inv_lsa(inv_lsa(inv_rs(u, 18), 15, 0xefc6_0000), 7, 0x9d2c_5680),
        11,
    )
}

pub fn crack_seed_from_nth(u: u32, n: usize, range: Range<u32>) -> Option<u32> {
    //Unfortunately we use brute force here. Is there an analytic attack?
    for candidate in range {
        if u == MersenneTwister::initialize(candidate).nth(n).unwrap() {
            return Some(candidate);
        }
    }
    None
}
<commit_msg>Address another clippy warning<commit_after>use std::ops::Range;
pub const STATE_SIZE: usize = 624;

// The following code is a direct translation of the pseudocode found on Wikipedia

/*
 // Create a length 624 array to store the state of the generator
 int[0..623] MT
 int index = 0
*/

pub struct MersenneTwister {
    mt: [u32; STATE_SIZE],
    index: usize,
}

impl MersenneTwister {
    /*
     // Initialize the generator from a seed
     function initialize_generator(int seed) {
         index := 0
         MT[0] := seed
         for i from 1 to 623 { // loop over each element
             MT[i] := lowest 32 bits of
                (1812433253 * (MT[i-1] xor (right shift by 30 bits(MT[i-1]))) + i) // 0x6c078965
         }
     }
    */

    pub fn initialize(seed: u32) -> Self {
        let mut mt = [0; STATE_SIZE];
        mt[0] = seed;
        for i in 1..STATE_SIZE {
            mt[i] = (mt[i - 1] ^ (mt[i - 1] >> 30))
                .wrapping_mul(0x6c07_8965)
                .wrapping_add(i as u32);
        }
        MersenneTwister { mt, index: 0 }
    }

    /*
     // Generate an array of 624 untempered numbers
     function generate_numbers() {
         for i from 0 to 623 {
             int y := (MT[i] and 0x80000000)           // bit 31 (32nd bit) of MT[i]
                + (MT[(i+1) mod 624] and 0x7fffffff)   // bits 0-30 (first 31 bits) of MT[...]
             MT[i] := MT[(i + 397) mod 624] xor (right shift by 1 bit(y))
             if (y mod 2) != 0 { // y is odd
                 MT[i] := MT[i] xor (2567483615) // 0x9908b0df
             }
         }
     }
    */

    fn generate(&mut self) {
        for i in 0..STATE_SIZE {
            let mt = &mut self.mt;
            let y = (mt[i] & 0x8000_0000) | (mt[(i + 1) % STATE_SIZE] & 0x7fff_ffff);
            mt[i] = mt[(i + 397) % STATE_SIZE] ^ (y >> 1);
            if y % 2 != 0 {
                mt[i] ^= 0x9908_b0df;
            }
        }
    }

    pub fn initialize_with_state(mt: [u32; STATE_SIZE]) -> MersenneTwister {
        MersenneTwister { mt, index: 0 }
    }
}

impl Iterator for MersenneTwister {
    type Item = u32;
    /*
     // Extract a tempered pseudorandom number based on the index-th value,
     // calling generate_numbers() every 624 numbers
     function extract_number() {
         if index == 0 {
             generate_numbers()
         }

         int y := MT[index]
         y := y xor (right shift by 11 bits(y))
         y := y xor (left shift by 7 bits(y) and (2636928640)) // 0x9d2c5680
         y := y xor (left shift by 15 bits(y) and (4022730752)) // 0xefc60000
         y := y xor (right shift by 18 bits(y))

         index := (index + 1) mod 624
         return y
     }

    */

    fn next(&mut self) -> Option<u32> {
        if self.index == 0 {
            self.generate();
        }
        let y = temper(self.mt[self.index]);
        self.index = (self.index + 1) % STATE_SIZE;
        Some(y)
    }
}

fn temper(mut y: u32) -> u32 {
    y ^= y >> 11;
    y ^= (y << 7) & 0x9d2c_5680;
    y ^= (y << 15) & 0xefc6_0000;
    y ^= y >> 18;
    y
}

fn inv_rs(mut u: u32, k: u32) -> u32 {
    assert!(k >= 1);
    let mut v = u;
    //Would profit from range_inclusive and std::u32::BITS
    for _ in 0..32 / k + 1 {
        u >>= k;
        v ^= u;
    }
    v
}

fn inv_lsa(u: u32, k: u32, c: u32) -> u32 {
    assert!(k >= 1);
    let mut v = u;
    //Would profit from std::u32::BITS
    for _ in 0..32 / k {
        v = u ^ (v << k & c);
    }
    v
}

pub fn untemper(u: u32) -> u32 {
    inv_rs(
        inv_lsa(inv_lsa(inv_rs(u, 18), 15, 0xefc6_0000), 7, 0x9d2c_5680),
        11,
    )
}

pub fn crack_seed_from_nth(u: u32, n: usize, range: Range<u32>) -> Option<u32> {
    //Unfortunately we use brute force here. Is there an analytic attack?
    for candidate in range {
        if u == MersenneTwister::initialize(candidate).nth(n).unwrap() {
            return Some(candidate);
        }
    }
    None
}
<|endoftext|>