<commit_before>use std::mem::swap;
use cgmath::*;
use mesh::{Model, Mesh, Vertex};
use memory::bytes_to_typed;
use std::fs;
use utils::get_full_path;
use std::io::{Read, Seek, SeekFrom};

const CHUNK_MAIN:         u16 = 0x4D4D;
const CHUNK_VERSION:      u16 = 0x0002;
const CHUNK_OBJMESH:      u16 = 0x3D3D;
const CHUNK_MESHVERSION:  u16 = 0x3D3E;
const CHUNK_MASTERSCALE:  u16 = 0x0100;
const CHUNK_OBJBLOCK:     u16 = 0x4000;
const CHUNK_TRIMESH:      u16 = 0x4100;
const CHUNK_VERTLIST:     u16 = 0x4110;
const CHUNK_FACELIST:     u16 = 0x4120;
const CHUNK_FACEMAT:      u16 = 0x4130;
const CHUNK_MAPLIST:      u16 = 0x4140;
const CHUNK_SMOOTHING:    u16 = 0x4150;
const CHUNK_TRMATRIX:     u16 = 0x4160;
const CHUNK_LIGHT:        u16 = 0x4600;
const CHUNK_SPOTLIGHT:    u16 = 0x4610;
const CHUNK_CAMERA:       u16 = 0x4700;
const CHUNK_MATERIAL:     u16 = 0xAFFF;
const CHUNK_MATNAME:      u16 = 0xA000;
const CHUNK_TEXTURE:      u16 = 0xA200;
const CHUNK_MAPFILE:      u16 = 0xA300;
const CHUNK_KEYFRAMER:    u16 = 0xB000;

struct Reader {
    reader: fs::File
}

pub struct Loader3ds {
    reader: Reader,
    model: Model,
    mesh: Mesh,
}

struct Header3ds {
    id: u16,
    size: u32,
    left_bytes: u32,
}

impl Header3ds {
    fn check_end(&self) -> Result<u32, String> {
        if self.left_bytes == 0 {
            Ok(self.size)
        } else {
            Err(format!("error length chunk in end for id = 0x{:x} left {} bytes", self.id, self.left_bytes))
        }
    }

    fn update_left(&mut self, read_bytes: u32) -> Result<(), String> {
        if self.left_bytes < read_bytes {
            return Err(format!("for chunk id = 0x{:x}, real read {} bytes, left {} bytes", self.id, read_bytes, self.left_bytes));
        } else {
            self.left_bytes -= read_bytes;
            Ok(())
        }
    }
}

impl Reader {
    fn new(filepath: &str) -> Result<Reader, String> {
        let f = match fs::File::open(filepath) {
            Ok(f) => f,
            Err(e) => return Err(format!("Can't open file \"{}\" with error: \"{}\"", filepath, e))
        };
        Ok(Reader {reader: f})
    }

    #[allow(dead_code)]
    fn get_u16(&mut self, header: &mut Header3ds) -> Result<u16, String> {
        try!(header.update_left(2));
        let mut buff = [0x0; 2];
        match self.reader.read(&mut buff) {
            Ok(_) => Ok((buff[0] as u16) + ((buff[1] as u16) << 8)),
            Err(e) => Err(format!("can't read 2 bytes, err = \"{}\"", e))
        }
    }

    fn get_u16_vec(&mut self, header: &mut Header3ds, num: usize) -> Result<Vec<u16>, String> {
        try!(header.update_left(2*num as u32));
        let mut buff: Vec<u8> = vec![0x0; 2*num];
        match self.reader.read(&mut buff) {
            Ok(_) => Ok(bytes_to_typed::<u16>(&mut buff).iter().cloned().collect()),
            Err(e) => Err(format!("can't read {} bytes, err = \"{}\"", 2*num, e))
        }
    }

    #[allow(dead_code)]
    fn get_u32(&mut self, header: &mut Header3ds) -> Result<u32, String> {
        try!(header.update_left(4));
        let mut buff = [0x0; 4];
        match self.reader.read(&mut buff) {
            Ok(_) => Ok((buff[0] as u32) + ((buff[1] as u32) << 8) + ((buff[2] as u32) << 16) + ((buff[3] as u32) << 24)),
            Err(e) => Err(format!("can't read 4 bytes, err = \"{}\"", e))
        }
    }

    fn get_f32_vec(&mut self, header: &mut Header3ds, num: usize) -> Result<Vec<f32>, String> {
        try!(header.update_left(4*num as u32));
        let mut buff: Vec<u8> = vec![0x0; 4*num];
        match self.reader.read(&mut buff) {
            Ok(_) => Ok(bytes_to_typed::<f32>(&mut buff).iter().cloned().collect()),
            Err(e) => Err(format!("can't read {} bytes, err = \"{}\"", 4*num, e))
        }
    }

    fn skip_string(&mut self, header: &mut Header3ds) -> Result<u32, String> {
        let mut buff: Vec<u8> = vec![0xFF];
        let mut size = 0;
        while buff[0] != 0 {
            match self.reader.read(&mut buff) {
                Ok(_) => {},
                Err(e) => return Err(format!("can't read 1 bytes, err = \"{}\"", e))
            };
            try!(header.update_left(1));
            size += 1;
        }
        Ok(size)
    }

    fn skip(&mut self, header: &mut Header3ds) -> Result<u32, String> {
        match self.reader.seek(SeekFrom::Current(header.left_bytes as i64)) {
            Ok(_) => {header.left_bytes = 0; return Ok(header.size)},
            Err(e) => return Err(format!("seek file error \"{}\"for chunk id = 0x{:x}", e, header.id))
        };
    }

    fn get_header(&mut self) -> Result<Header3ds, String> {
        let id;
        let size;
        let mut buff = [0x0; 6];
        match self.reader.read(&mut buff) {
            Ok(_) => {
                id = (buff[0] as u16) + ((buff[1] as u16) << 8);
                size = (buff[2] as u32) + ((buff[3] as u32) << 8) + ((buff[4] as u32) << 16) + ((buff[5] as u32) << 24);
            },
            Err(e) => return Err(format!("can't read 6 bytes, err = \"{}\"", e))
        };

        if size < 6 {
            return Err(format!("for chunk id = 0x{:x}, header real size is 6 bytes, but in header is {} bytes", id, size));
        }

        Ok(Header3ds {
            id: id,
            size: size,
            left_bytes: size - 6,
        })
    }
}

impl Loader3ds {
    fn read_children(&mut self, header: &mut Header3ds) -> Result<u32, String> {
        while header.left_bytes != 0 {
            try!(header.update_left(try!(self.read_chunk())));
        }
        Ok(try!(header.check_end()))
    }

    fn skip_chunk(&mut self, header: &mut Header3ds) -> Result<u32, String> {
        try!(self.reader.skip(header));
        Ok(try!(header.check_end()))
    }

    fn read_objblok(&mut self, header: &mut Header3ds) -> Result<u32, String> {
        try!(self.reader.skip_string(header));
        self.read_children(header)
    }

    fn read_trimesh(&mut self, header: &mut Header3ds) -> Result<u32, String> {
        let result = try!(self.read_children(header));
        try!(self.mesh.calc_normal());
        let mut mesh = Mesh::new();
        swap(&mut mesh, &mut self.mesh);
        self.model.add(mesh);

        Ok(result)
    }

    fn read_vertlist(&mut self, header: &mut Header3ds) -> Result<u32, String> {
        let num = try!(self.reader.get_u16(header)) as usize;
        let mut vb = Vec::<Vertex>::with_capacity(num);
        for v in try!(self.reader.get_f32_vec(header, num * 3)).chunks(3) {
            vb.push(Vertex{position: Vector3::new(v[0], v[2], v[1]),
                           normal: Vector3::new(0.0_f32, 0.0_f32, 0.0_f32),
                           tex: Vector2::new(0.0_f32, 0.0_f32)});
        }
        self.mesh.vertex(vb);

        Ok(try!(header.check_end()))
    }

    fn read_facelist(&mut self, header: &mut Header3ds) -> Result<u32, String> {
        let num = try!(self.reader.get_u16(header)) as usize;
        let mut ib = Vec::<u32>::with_capacity(num * 3);
        for i in try!(self.reader.get_u16_vec(header, num * 4)).chunks(4) {
            ib.push(i[0] as u32);
            ib.push(i[1] as u32);
            ib.push(i[2] as u32);
        }
        try!(self.mesh.index(ib));
        try!(self.read_children(header));

        Ok(try!(header.check_end()))
    }

    fn read_chunk(& mut self) -> Result<u32, String> {
        let mut header = try!(self.reader.get_header());
        // println!("id = 0x{:x}; size={};", header.id, header.size);
        match header.id {
            CHUNK_MAIN         => self.read_children(&mut header),
            CHUNK_VERSION      => self.skip_chunk(&mut header),
            CHUNK_OBJMESH      => self.read_children(&mut header),
            CHUNK_MESHVERSION  => self.skip_chunk(&mut header),
            CHUNK_MASTERSCALE  => self.skip_chunk(&mut header),
            CHUNK_OBJBLOCK     => self.read_objblok(&mut header),
            CHUNK_TRIMESH      => self.read_trimesh(&mut header),
            CHUNK_VERTLIST     => self.read_vertlist(&mut header),
            CHUNK_FACELIST     => self.read_facelist(&mut header),
            CHUNK_FACEMAT      => self.skip_chunk(&mut header),
            CHUNK_MAPLIST      => self.skip_chunk(&mut header),
            CHUNK_SMOOTHING    => self.skip_chunk(&mut header),
            CHUNK_TRMATRIX     => self.skip_chunk(&mut header),
            CHUNK_LIGHT        => self.skip_chunk(&mut header),
            CHUNK_SPOTLIGHT    => self.skip_chunk(&mut header),
            CHUNK_CAMERA       => self.skip_chunk(&mut header),
            CHUNK_MATERIAL     => self.skip_chunk(&mut header),
            CHUNK_MATNAME      => self.skip_chunk(&mut header),
            CHUNK_TEXTURE      => self.skip_chunk(&mut header),
            CHUNK_MAPFILE      => self.skip_chunk(&mut header),
            CHUNK_KEYFRAMER    => self.skip_chunk(&mut header),
            _ => Err(format!("unknown chank id = 0x{:x}", header.id))
        }
    }

    pub fn load(filename: &str) -> Result<Model, String> {
        let filepath = try!(get_full_path(filename));
        let mut this = Loader3ds {
            reader: try!(Reader::new(&filepath)),
            model: Model::new(),
            mesh: Mesh::new(),
        };
        try!(this.read_chunk());

        Ok(this.model)
    }
}
<commit_msg>import texture in 3ds<commit_after>use std::mem::swap;
use cgmath::*;
use mesh::{Model, Mesh, Vertex};
use memory::bytes_to_typed;
use std::fs;
use std::path::PathBuf;
use utils::get_full_path;
use std::io::{Read, Seek, SeekFrom};

const CHUNK_MAIN:         u16 = 0x4D4D;
const CHUNK_VERSION:      u16 = 0x0002;
const CHUNK_OBJMESH:      u16 = 0x3D3D;
const CHUNK_MESHVERSION:  u16 = 0x3D3E;
const CHUNK_MASTERSCALE:  u16 = 0x0100;
const CHUNK_OBJBLOCK:     u16 = 0x4000;
const CHUNK_TRIMESH:      u16 = 0x4100;
const CHUNK_VERTLIST:     u16 = 0x4110;
const CHUNK_FACELIST:     u16 = 0x4120;
const CHUNK_FACEMAT:      u16 = 0x4130;
const CHUNK_MAPLIST:      u16 = 0x4140;
const CHUNK_SMOOTHING:    u16 = 0x4150;
const CHUNK_TRMATRIX:     u16 = 0x4160;
const CHUNK_LIGHT:        u16 = 0x4600;
const CHUNK_SPOTLIGHT:    u16 = 0x4610;
const CHUNK_CAMERA:       u16 = 0x4700;
const CHUNK_MATERIAL:     u16 = 0xAFFF;
const CHUNK_MATNAME:      u16 = 0xA000;
const CHUNK_AMBIENT:      u16 = 0xA010;
const CHUNK_DIFFUSE:      u16 = 0xA020;
const CHUNK_SPECULAR:     u16 = 0xA030;
const CHUNK_TEXTURE:      u16 = 0xA200;
const CHUNK_BUMPMAP:      u16 = 0xA230;
const CHUNK_MAPFILE:      u16 = 0xA300;
const CHUNK_MAPPARAM:     u16 = 0xA351;
const CHUNK_MAPUSCALE:    u16 = 0xA354;
const CHUNK_MAPVSCALE:    u16 = 0xA356;
const CHUNK_MAPUOFFSET:   u16 = 0xA358;
const CHUNK_MAPVOFFSET:   u16 = 0xA35A;
const CHUNK_KEYFRAMER:    u16 = 0xB000;

fn chunk_id_to_str(chunk_id: u16) -> String {
    match chunk_id {
        0x4D4D => "CHUNK_MAIN",
        0x0002 => "CHUNK_VERSION",
        0x3D3D => "CHUNK_OBJMESH",
        0x3D3E => "CHUNK_MESHVERSION",
        0x0100 => "CHUNK_MASTERSCALE",
        0x4000 => "CHUNK_OBJBLOCK",
        0x4100 => "CHUNK_TRIMESH",
        0x4110 => "CHUNK_VERTLIST",
        0x4120 => "CHUNK_FACELIST",
        0x4130 => "CHUNK_FACEMAT",
        0x4140 => "CHUNK_MAPLIST",
        0x4150 => "CHUNK_SMOOTHING",
        0x4160 => "CHUNK_TRMATRIX",
        0x4600 => "CHUNK_LIGHT",
        0x4610 => "CHUNK_SPOTLIGHT",
        0x4700 => "CHUNK_CAMERA",
        0xAFFF => "CHUNK_MATERIAL",
        0xA000 => "CHUNK_MATNAME",
        0xA010 => "CHUNK_AMBIENT",
        0xA020 => "CHUNK_DIFFUSE",
        0xA030 => "CHUNK_SPECULAR",
        0xA200 => "CHUNK_TEXTURE",
        0xA230 => "CHUNK_BUMPMAP",
        0xA300 => "CHUNK_MAPFILE",
        0xA351 => "CHUNK_MAPPARAM",
        0xA354 => "CHUNK_MAPUSCALE",
        0xA356 => "CHUNK_MAPVSCALE",
        0xA358 => "CHUNK_MAPUOFFSET",
        0xA35A => "CHUNK_MAPVOFFSET",
        0xB000 => "CHUNK_KEYFRAMER",
        _ => "CHUNK_UNKNOWN",
    }.to_string()
}

struct Reader {
    reader: fs::File,
    current_dir: PathBuf,

}

pub struct Loader3ds {
    reader: Reader,
    model: Model,
    mesh: Mesh,
}

struct Header3ds {
    id: u16,
    size: u32,
    left_bytes: u32,
}

impl Header3ds {
    fn check_end(&self) -> Result<u32, String> {
        if self.left_bytes == 0 {
            Ok(self.size)
        } else {
            Err(format!("error length chunk in end for id = 0x{:x} left {} bytes", self.id, self.left_bytes))
        }
    }

    fn update_left(&mut self, read_bytes: u32) -> Result<(), String> {
        if self.left_bytes < read_bytes {
            return Err(format!("for chunk id = 0x{:x}, real read {} bytes, left {} bytes", self.id, read_bytes, self.left_bytes));
        } else {
            self.left_bytes -= read_bytes;
            Ok(())
        }
    }
}

impl Reader {
    fn new(filepath: &str) -> Result<Reader, String> {
        let mut current_dir = PathBuf::from(filepath);
        if !current_dir.pop() {
            return Err(format!("not found parent dir for filepath {}", filepath));
        }

        let f = match fs::File::open(filepath) {
            Ok(f) => f,
            Err(e) => return Err(format!("Can't open file \"{}\" with error: \"{}\"", filepath, e))
        };

        Ok(Reader {
            reader: f,
            current_dir: current_dir,
        })
    }

    #[allow(dead_code)]
    fn get_u16(&mut self, header: &mut Header3ds) -> Result<u16, String> {
        try!(header.update_left(2));
        let mut buff = [0x0; 2];
        match self.reader.read(&mut buff) {
            Ok(_) => Ok((buff[0] as u16) + ((buff[1] as u16) << 8)),
            Err(e) => Err(format!("can't read 2 bytes, err = \"{}\"", e))
        }
    }

    fn get_u16_vec(&mut self, header: &mut Header3ds, num: usize) -> Result<Vec<u16>, String> {
        try!(header.update_left(2*num as u32));
        let mut buff: Vec<u8> = vec![0x0; 2*num];
        match self.reader.read(&mut buff) {
            Ok(_) => Ok(bytes_to_typed::<u16>(&mut buff).iter().cloned().collect()),
            Err(e) => Err(format!("can't read {} bytes, err = \"{}\"", 2*num, e))
        }
    }

    #[allow(dead_code)]
    fn get_u32(&mut self, header: &mut Header3ds) -> Result<u32, String> {
        try!(header.update_left(4));
        let mut buff = [0x0; 4];
        match self.reader.read(&mut buff) {
            Ok(_) => Ok((buff[0] as u32) + ((buff[1] as u32) << 8) + ((buff[2] as u32) << 16) + ((buff[3] as u32) << 24)),
            Err(e) => Err(format!("can't read 4 bytes, err = \"{}\"", e))
        }
    }

    fn get_f32_vec(&mut self, header: &mut Header3ds, num: usize) -> Result<Vec<f32>, String> {
        try!(header.update_left(4*num as u32));
        let mut buff: Vec<u8> = vec![0x0; 4*num];
        match self.reader.read(&mut buff) {
            Ok(_) => Ok(bytes_to_typed::<f32>(&mut buff).iter().cloned().collect()),
            Err(e) => Err(format!("can't read {} bytes, err = \"{}\"", 4*num, e))
        }
    }

    fn read_string(&mut self, header: &mut Header3ds, size: u32) -> Result<String, String> {
        try!(header.update_left(size));
        let mut buff: Vec<u8> = vec![0x0; size as usize];
        match self.reader.read(&mut buff) {
            Ok(_) => {},
            Err(e) => return Err(format!("can't read {} bytes, err = \"{}\"", size, e))
        };

        buff.pop().unwrap();
        match String::from_utf8(buff) {
            Ok(v) => Ok(v),
            Err(_) => Err(format!("can't read string"))
        }
    }

    fn skip_string(&mut self, header: &mut Header3ds) -> Result<u32, String> {
        let mut buff: Vec<u8> = vec![0xFF];
        let mut size = 0;
        while buff[0] != 0 {
            match self.reader.read(&mut buff) {
                Ok(_) => {},
                Err(e) => return Err(format!("can't read 1 bytes, err = \"{}\"", e))
            };
            try!(header.update_left(1));
            size += 1;
        }
        Ok(size)
    }

    fn skip(&mut self, header: &mut Header3ds) -> Result<u32, String> {
        match self.reader.seek(SeekFrom::Current(header.left_bytes as i64)) {
            Ok(_) => {header.left_bytes = 0; return Ok(header.size)},
            Err(e) => return Err(format!("seek file error \"{}\"for chunk id = 0x{:x}", e, header.id))
        };
    }

    fn get_header(&mut self) -> Result<Header3ds, String> {
        let id;
        let size;
        let mut buff = [0x0; 6];
        match self.reader.read(&mut buff) {
            Ok(_) => {
                id = (buff[0] as u16) + ((buff[1] as u16) << 8);
                size = (buff[2] as u32) + ((buff[3] as u32) << 8) + ((buff[4] as u32) << 16) + ((buff[5] as u32) << 24);
            },
            Err(e) => return Err(format!("can't read 6 bytes, err = \"{}\"", e))
        };

        if size < 6 {
            return Err(format!("for chunk id = 0x{:x}, header real size is 6 bytes, but in header is {} bytes", id, size));
        }

        Ok(Header3ds {
            id: id,
            size: size,
            left_bytes: size - 6,
        })
    }
}

impl Loader3ds {
    fn read_children(&mut self, header: &mut Header3ds) -> Result<u32, String> {
        while header.left_bytes != 0 {
            try!(header.update_left(try!(self.read_chunk())));
        }
        Ok(try!(header.check_end()))
    }

    fn skip_chunk(&mut self, header: &mut Header3ds) -> Result<u32, String> {
        try!(self.reader.skip(header));
        Ok(try!(header.check_end()))
    }

    fn read_objblok(&mut self, header: &mut Header3ds) -> Result<u32, String> {
        try!(self.reader.skip_string(header));
        self.read_children(header)
    }

    fn read_trimesh(&mut self, header: &mut Header3ds) -> Result<u32, String> {
        let result = try!(self.read_children(header));
        if self.mesh.vertex_buffer.len() != 0 || self.mesh.index_buffer.len() != 0 {
            try!(self.mesh.calc_normal());
            let mut mesh = Mesh::new();
            swap(&mut mesh, &mut self.mesh);
            self.model.add(mesh);
        }

        Ok(result)
    }

    fn read_vertlist(&mut self, header: &mut Header3ds) -> Result<u32, String> {
        let num = try!(self.reader.get_u16(header)) as usize;
        // println!("vertlist = {}", num);
        if num != 0 {
            let mut vb = Vec::<Vertex>::with_capacity(num);
            for v in try!(self.reader.get_f32_vec(header, num * 3)).chunks(3) {
                vb.push(Vertex{position: Vector3::new(v[0], v[2], v[1]),
                               normal: Vector3::new(0.0_f32, 0.0_f32, 0.0_f32),
                               tex: Vector2::new(0.0_f32, 0.0_f32)});
            }
            self.mesh.vertex(vb);
        }

        Ok(try!(header.check_end()))
    }

    fn read_facelist(&mut self, header: &mut Header3ds) -> Result<u32, String> {
        let num = try!(self.reader.get_u16(header)) as usize;
        // println!("facelist = {}", num);
        if num != 0 {
            let mut ib = Vec::<u32>::with_capacity(num * 3);
            for i in try!(self.reader.get_u16_vec(header, num * 4)).chunks(4) {
                ib.push(i[0] as u32);
                ib.push(i[1] as u32);
                ib.push(i[2] as u32);
            }
            try!(self.mesh.index(ib));
        }
        try!(self.read_children(header));

        Ok(try!(header.check_end()))
    }

    fn read_texcoord(&mut self, header: &mut Header3ds) -> Result<u32, String> {
        let num = try!(self.reader.get_u16(header)) as usize;
        if self.mesh.vertex_buffer.len() != num {
            return Err(format!("len of vertex_buffer is {}, len of texture coordinates is {}",
                               self.mesh.vertex_buffer.len(), num));
        }

        let vb = &mut self.mesh.vertex_buffer;
        for (i, v) in try!(self.reader.get_f32_vec(header, num * 2)).chunks(2).enumerate() {
            vb[i].tex = Vector2::new(v[0], v[1]);
        }

        Ok(try!(header.check_end()))
    }

    fn read_texfile(&mut self, header: &mut Header3ds) -> Result<u32, String> {
        let size = header.left_bytes;
        let filename = try!(self.reader.read_string(header, size));
        try!(self.mesh.texture.load_from_dir(&self.reader.current_dir, &filename));

        Ok(try!(header.check_end()))
    }

    fn read_chunk(& mut self) -> Result<u32, String> {
        let mut header = try!(self.reader.get_header());
        println!("{}(0x{:x}); size={};", chunk_id_to_str(header.id), header.id, header.size);
        match header.id {
            CHUNK_MAIN         => self.read_children(&mut header),
            CHUNK_VERSION      => self.skip_chunk(&mut header),
            CHUNK_OBJMESH      => self.read_children(&mut header),
            CHUNK_MESHVERSION  => self.skip_chunk(&mut header),
            CHUNK_MASTERSCALE  => self.skip_chunk(&mut header),
            CHUNK_OBJBLOCK     => self.read_objblok(&mut header),
            CHUNK_TRIMESH      => self.read_trimesh(&mut header),
            CHUNK_VERTLIST     => self.read_vertlist(&mut header),
            CHUNK_FACELIST     => self.read_facelist(&mut header),
            CHUNK_FACEMAT      => self.skip_chunk(&mut header),
            CHUNK_MAPLIST      => self.read_texcoord(&mut header),
            CHUNK_SMOOTHING    => self.skip_chunk(&mut header),
            CHUNK_TRMATRIX     => self.skip_chunk(&mut header),
            CHUNK_LIGHT        => self.skip_chunk(&mut header),
            CHUNK_SPOTLIGHT    => self.skip_chunk(&mut header),
            CHUNK_CAMERA       => self.skip_chunk(&mut header),
            CHUNK_MATERIAL     => self.read_children(&mut header),
            CHUNK_MATNAME      => self.skip_chunk(&mut header),
            CHUNK_AMBIENT      => self.skip_chunk(&mut header),
            CHUNK_DIFFUSE      => self.skip_chunk(&mut header),
            CHUNK_SPECULAR     => self.skip_chunk(&mut header),
            CHUNK_TEXTURE      => self.read_children(&mut header),
            CHUNK_BUMPMAP      => self.skip_chunk(&mut header),
            CHUNK_MAPFILE      => self.read_texfile(&mut header),
            CHUNK_MAPPARAM     => self.skip_chunk(&mut header),
            CHUNK_MAPUSCALE    => self.skip_chunk(&mut header),
            CHUNK_MAPVSCALE    => self.skip_chunk(&mut header),
            CHUNK_MAPUOFFSET   => self.skip_chunk(&mut header),
            CHUNK_MAPVOFFSET   => self.skip_chunk(&mut header),
            CHUNK_KEYFRAMER    => self.skip_chunk(&mut header),
            _ => Err(format!("unknown chank id = 0x{:x}", header.id))
        }
    }

    pub fn load(filename: &str) -> Result<Model, String> {
        let filepath = try!(get_full_path(filename));
        let mut this = Loader3ds {
            reader: try!(Reader::new(&filepath)),
            model: Model::new(),
            mesh: Mesh::new(),
        };
        try!(this.read_chunk());

        Ok(this.model)
    }
}
<|endoftext|>


<commit_before>// Copyright (C) 2017, 2018 Falco Hirschenberger, Élisabeth Henry.
//
// This file is part of Crowbook.
//
// Crowbook is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation, either version 2.1 of the License, or
// (at your option) any later version.
//
// Crowbook is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Crowbook.  If not, see <http://www.gnu.org/licenses/>.

use book::Book;
use text_view::view_as_text;
use style;

use std::fmt;
use std::f64;
use hyphenation;
use hyphenation::{Hyphenation, Language};
#[cfg(feature = "nightly")]
use punkt::{SentenceTokenizer, TrainingData};
#[cfg(feature = "nightly")]
use punkt::params::Standard;

/* Only collected on nightly */
struct AdvancedStats {
    pub sentence_count: usize,
    pub flesch_score: f64,
}

struct ChapterStats {
    pub name: String,
    pub word_count: usize,
    pub char_count: usize,
    pub syllable_count: usize,
    pub advanced: Option<AdvancedStats>,
}

impl ChapterStats {
    #[cfg(feature = "nightly")]
    pub fn fill_advanced(&mut self, lang: Language, text: &str) {
        let (td, flesch_func) = Stats::language_data(lang);
        let sc = SentenceTokenizer::<Standard>::new(&text, &td).count();
        let stats = AdvancedStats {
            sentence_count: sc,
            flesch_score: f64::NAN,
        };
        self.advanced = Some(stats);
        let score = if let Some(ref f) = flesch_func {
            f(self)
        } else {
            f64::NAN
        };
        if let Some(ref mut adv) = self.advanced {
            adv.flesch_score = score;
        }
    }

    #[cfg(not(feature = "nightly"))]
    pub fn fill_advanced(&mut self, _: Language, _: &str) {
    }
}

pub struct Stats {
    chapters: Vec<ChapterStats>,
    advanced: bool,
}

impl Stats {
    pub fn new(book: &Book, advanced: bool) -> Stats {
        let lang = book.options.get_str("lang").unwrap();
        let lang = Stats::language_from_str(lang);
        let mut stats = Stats { chapters: vec![], advanced: advanced };

        for c in &book.chapters {
            let name = c.filename.clone();
            let text = view_as_text(&c.content);
            let words: Vec<_> = text.split_whitespace().collect();
            let wc = words.len();
            // Note: Don't count the bytes with `len()` count the actual (multibyte-)characters
            let cc = text.chars().count();
            let corp = hyphenation::load(lang).unwrap();
            // Count the number of syllables for earch word.
            let syl = words
                .iter()
                .fold(0, |acc, w| acc + w.opportunities(&corp).len() + 1);
            let mut chapter_stats = ChapterStats {
                name: name,
                word_count: wc,
                char_count: cc,
                syllable_count: syl,
                advanced: None,
            };
            if advanced {
                chapter_stats.fill_advanced(lang, &text);
            }
            stats.chapters.push(chapter_stats);
        }
        stats
    }

    // Returns the Languuage (defined by Hyphenation crate) according to the str code
    fn language_from_str(lang: &str) -> Language {
        // FIXME: handle case where lang is e.g. fr_FR or en_GB
        match lang {
            "cz" => Language::Czech,
            "da" => Language::Danish,
            "nl" => Language::Dutch,
            "en" => Language::English_GB,
            "et" => Language::Estonian,
            "fi" => Language::Finnish,
            "fr" => Language::French,
            "de" => Language::German_1996,
            "el" => Language::Greek_Poly,
            "it" => Language::Italian,
            "no" => Language::Norwegian_Bokmal,
            "pl" => Language::Polish,
            "pt" => Language::Portuguese,
            "sl" => Language::Slovenian,
            "es" => Language::Spanish,
            "sv" => Language::Swedish,
            "tk" => Language::Turkish,
            _ =>  {
                warn!(
                    //FIXME: display localized warning (or use Result?)
                    "Unknown language: '{}' for text statistics, using 'en' default.",
                    lang
                );
                Language::English_GB
            },
        }
    }

    // The Flesch reading index formulae for different languages are from the `YoastSEO.js` text
    // analysis library. See: https://github.com/Yoast/YoastSEO.js/issues/267
    #[cfg(feature = "nightly")]
    fn language_data(
        lang: Language
    ) -> (
        TrainingData,
        Option<Box<Fn(&ChapterStats) -> f64>>,
    ) {
        match lang {
            Language::Czech => (TrainingData::czech(), None),
            Language::Danish => (TrainingData::danish(), None),
            Language::Dutch => (
                TrainingData::dutch(),
                Some(Box::new(|s: &ChapterStats| {
                    if let Some(ref adv) = s.advanced {
                        206.84 - 77.0 * (s.syllable_count as f64 / s.word_count as f64)
                            - 0.93 * (s.word_count as f64 / adv.sentence_count as f64)
                    } else {
                        unreachable!("If nightly build is set, it should always return Some(thing)");
                    }
                })),
            ),
            Language::English_GB => (
                TrainingData::english(),
                Some(Box::new(|s: &ChapterStats| {
                    if let Some(ref adv) = s.advanced {
                    206.835 - 0.77 * (s.syllable_count as f64 * 100.0 / s.word_count as f64)
                        - 0.93 * (s.word_count as f64 / adv.sentence_count as f64)
                    } else {
                        unreachable!();
                    }})),
            ),
            Language::Estonian => (TrainingData::estonian(), None),
            Language::Finnish => (TrainingData::finnish(), None),
            Language::French => (
                TrainingData::french(),
                Some(Box::new(|s: &ChapterStats| {
                    if let Some(ref adv) = s.advanced {
                        207.0 - 1.015 * (s.word_count as f64 / adv.sentence_count as f64)
                            - 73.6 * (s.syllable_count as f64 / s.word_count as f64)
                    } else {
                        unreachable!();
                    }})),
            ),
            Language::German_1996 => (
                TrainingData::german(),
                Some(Box::new(|s: &ChapterStats| {
                    if let Some(ref adv) = s.advanced {
                        180.0 - (s.word_count as f64 / adv.sentence_count as f64)
                            - 84.6 * (s.syllable_count as f64 / s.word_count as f64)
                    } else {
                        unreachable!();
                    }
                })),
            ),
            Language::Greek_Poly => (TrainingData::greek(), None),
            Language::Italian => (
                TrainingData::italian(),
                Some(Box::new(|s: &ChapterStats| {
                    if let Some(ref adv) = s.advanced {
                        217.0 - 1.3 * (s.word_count as f64 / adv.sentence_count as f64)
                            - 60.0 * (s.syllable_count as f64 * 100.0 / s.word_count as f64)
                    } else {
                        unreachable!();
                    }
                })),
            ),
            Language::Norwegian_Bokmal => (TrainingData::norwegian(), None),
            Language::Polish => (TrainingData::polish(), None),
            Language::Portuguese => (TrainingData::portuguese(), None),
            Language::Slovenian => (TrainingData::slovene(), None),
            Language::Spanish => (
                TrainingData::spanish(),
                Some(Box::new(|s: &ChapterStats| {
                    if let Some(ref adv) = s.advanced {
                        206.84 - 1.02 * (s.word_count as f64 / adv.sentence_count as f64)
                        - 60.0 * (s.syllable_count as f64 * 100.0 / s.word_count as f64)
                    } else {
                        unreachable!();
                    }})),
            ),
            Language::Swedish => (TrainingData::swedish(), None),
            Language::Turkish => (TrainingData::turkish(), None),
            _ => {
                unreachable!("language_data should handle all casess returned by language_from_str");
            },
        }
    }

    fn flesch_text(score: f64) -> String {
        String::from(match score {
            s if s.is_nan() => "Not availabe",
            s if s < 30.0 => "Very difficult",
            s if s < 50.0 => "Difficult",
            s if s < 60.0 => "Fairly difficult",
            s if s < 70.0 => "Standard",
            s if s < 80.0 => "Fairly easy",
            s if s < 90.0 => "Easy",
            _ => "Very Easy",
        })
    }
}

impl fmt::Display for Stats {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let max_chapter_length = self.chapters
            .iter()
            .max_by_key(|e| e.name.chars().count())
            .unwrap()
            .name
            .chars()
            .count() + 3;
        if self.advanced {
            write!(
                f,
                "{:<width$} {:>8} {:>10} {:>7} {:>11} {:>11} {:>16} {:>29}\n---------\n",
                style::header(&lformat!("Chapter")),
                style::header(&lformat!("Chars")),
                style::header(&lformat!("Syllables")),
                style::header(&lformat!("Words")),
                style::header(&lformat!("Sentences")),
                style::header(&lformat!("Chars/Word")),
                style::header(&lformat!("Words/Sentence")),
                style::header(&lformat!("Flesch reading ease index")),
                width = max_chapter_length
            )?;
        } else {
            write!(
                f,
                "{:<width$} {:>8} {:>10} {:>11}\n---------\n",
                style::header(&lformat!("Chapter")),
                style::header(&lformat!("Chars")),
                style::header(&lformat!("Words")),
                style::header(&lformat!("Chars/Word")),
                width = max_chapter_length
            )?;
        }
        for c in &self.chapters {
            if let Some(ref adv) = c.advanced {
                write!(
                    f,
                    "{:<width$} {:>8} {:>10} {:>7} {:>11} {:>11.2} {:>16.2} {:>8.1} => {:>17}\n",
                    style::element(&c.name),
                    c.char_count,
                    c.syllable_count,
                    c.word_count,
                    adv.sentence_count,
                    c.char_count as f64 / c.word_count as f64,
                    c.word_count as f64 / adv.sentence_count as f64,
                    adv.flesch_score,
                    Self::flesch_text(adv.flesch_score),
                    width = max_chapter_length
                )?;
            } else {
                write!(
                    f,
                    "{:<width$} {:>8} {:>10} {:>11.2}\n",
                    style::element(&c.name),
                    c.char_count,
                    c.word_count,
                    c.char_count as f64 / c.word_count as f64,
                    width = max_chapter_length
                )?;
            }
        }
        let total = self.chapters.iter().fold((0, 0, 0, 0, 0.0, 0), |acc, c| {
            let sentence_count;
            let flesch_score;
            if let Some(ref adv) = c.advanced {
                sentence_count = adv.sentence_count;
                flesch_score = adv.flesch_score;
            } else {
                sentence_count = 0;
                flesch_score = f64::NAN;
            };
            (
                acc.0 + c.char_count,
                acc.1 + c.syllable_count,
                acc.2 + c.word_count,
                acc.3 + sentence_count,
                acc.4 + flesch_score,
                acc.5 + 1,
            )
        });
        if self.advanced {
            write!(
                f,
                "---------\n{:<width$} {:>8} {:>10} {:>7} {:>11} {:>11.2} {:>16.2} {:>8.1} => {:>17}\n",
                style::element(&lformat!("TOTAL:")),
                total.0,
                total.1,
                total.2,
                total.3,
                total.0 as f64 / total.2 as f64,
                total.2 as f64 / total.3 as f64,
                total.4 / total.5 as f64,
                width = max_chapter_length
            )
        } else {
            write!(
                f,
                "---------\n{:<width$} {:>8} {:>10} {:>11.2}\n",
                style::element(&lformat!("TOTAL:")),
                total.0,
                total.2,
                total.0 as f64 / total.2 as f64,
                width = max_chapter_length
            )
        }
    }
}
<commit_msg>Fix display of flesh index in summary line<commit_after>// Copyright (C) 2017, 2018 Falco Hirschenberger, Élisabeth Henry.
//
// This file is part of Crowbook.
//
// Crowbook is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation, either version 2.1 of the License, or
// (at your option) any later version.
//
// Crowbook is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Crowbook.  If not, see <http://www.gnu.org/licenses/>.

use book::Book;
use text_view::view_as_text;
use style;

use std::fmt;
use std::f64;
use hyphenation;
use hyphenation::{Hyphenation, Language};
#[cfg(feature = "nightly")]
use punkt::{SentenceTokenizer, TrainingData};
#[cfg(feature = "nightly")]
use punkt::params::Standard;

/* Only collected on nightly */
struct AdvancedStats {
    pub sentence_count: usize,
    pub flesch_score: f64,
}

struct ChapterStats {
    pub name: String,
    pub word_count: usize,
    pub char_count: usize,
    pub syllable_count: usize,
    pub advanced: Option<AdvancedStats>,
}

impl ChapterStats {
    #[cfg(feature = "nightly")]
    pub fn fill_advanced(&mut self, lang: Language, text: &str) {
        let (td, flesch_func) = Stats::language_data(lang);
        let sc = SentenceTokenizer::<Standard>::new(&text, &td).count();
        let stats = AdvancedStats {
            sentence_count: sc,
            flesch_score: f64::NAN,
        };
        self.advanced = Some(stats);
        let score = if let Some(ref f) = flesch_func {
            f(self)
        } else {
            f64::NAN
        };
        if let Some(ref mut adv) = self.advanced {
            adv.flesch_score = score;
        }
    }

    #[cfg(not(feature = "nightly"))]
    pub fn fill_advanced(&mut self, _: Language, _: &str) {
    }
}

pub struct Stats {
    chapters: Vec<ChapterStats>,
    advanced: bool,
}

impl Stats {
    pub fn new(book: &Book, advanced: bool) -> Stats {
        let lang = book.options.get_str("lang").unwrap();
        let lang = Stats::language_from_str(lang);
        let mut stats = Stats { chapters: vec![], advanced: advanced };

        for c in &book.chapters {
            let name = c.filename.clone();
            let text = view_as_text(&c.content);
            let words: Vec<_> = text.split_whitespace().collect();
            let wc = words.len();
            // Note: Don't count the bytes with `len()` count the actual (multibyte-)characters
            let cc = text.chars().count();
            let corp = hyphenation::load(lang).unwrap();
            // Count the number of syllables for earch word.
            let syl = words
                .iter()
                .fold(0, |acc, w| acc + w.opportunities(&corp).len() + 1);
            let mut chapter_stats = ChapterStats {
                name: name,
                word_count: wc,
                char_count: cc,
                syllable_count: syl,
                advanced: None,
            };
            if advanced {
                chapter_stats.fill_advanced(lang, &text);
            }
            stats.chapters.push(chapter_stats);
        }
        stats
    }

    // Returns the Languuage (defined by Hyphenation crate) according to the str code
    fn language_from_str(lang: &str) -> Language {
        // FIXME: handle case where lang is e.g. fr_FR or en_GB
        match lang {
            "cz" => Language::Czech,
            "da" => Language::Danish,
            "nl" => Language::Dutch,
            "en" => Language::English_GB,
            "et" => Language::Estonian,
            "fi" => Language::Finnish,
            "fr" => Language::French,
            "de" => Language::German_1996,
            "el" => Language::Greek_Poly,
            "it" => Language::Italian,
            "no" => Language::Norwegian_Bokmal,
            "pl" => Language::Polish,
            "pt" => Language::Portuguese,
            "sl" => Language::Slovenian,
            "es" => Language::Spanish,
            "sv" => Language::Swedish,
            "tk" => Language::Turkish,
            _ =>  {
                warn!(
                    //FIXME: display localized warning (or use Result?)
                    "Unknown language: '{}' for text statistics, using 'en' default.",
                    lang
                );
                Language::English_GB
            },
        }
    }

    // The Flesch reading index formulae for different languages are from the `YoastSEO.js` text
    // analysis library. See: https://github.com/Yoast/YoastSEO.js/issues/267
    #[cfg(feature = "nightly")]
    fn language_data(
        lang: Language
    ) -> (
        TrainingData,
        Option<Box<Fn(&ChapterStats) -> f64>>,
    ) {
        match lang {
            Language::Czech => (TrainingData::czech(), None),
            Language::Danish => (TrainingData::danish(), None),
            Language::Dutch => (
                TrainingData::dutch(),
                Some(Box::new(|s: &ChapterStats| {
                    if let Some(ref adv) = s.advanced {
                        206.84 - 77.0 * (s.syllable_count as f64 / s.word_count as f64)
                            - 0.93 * (s.word_count as f64 / adv.sentence_count as f64)
                    } else {
                        unreachable!("If nightly build is set, it should always return Some(thing)");
                    }
                })),
            ),
            Language::English_GB => (
                TrainingData::english(),
                Some(Box::new(|s: &ChapterStats| {
                    if let Some(ref adv) = s.advanced {
                    206.835 - 0.77 * (s.syllable_count as f64 * 100.0 / s.word_count as f64)
                        - 0.93 * (s.word_count as f64 / adv.sentence_count as f64)
                    } else {
                        unreachable!();
                    }})),
            ),
            Language::Estonian => (TrainingData::estonian(), None),
            Language::Finnish => (TrainingData::finnish(), None),
            Language::French => (
                TrainingData::french(),
                Some(Box::new(|s: &ChapterStats| {
                    if let Some(ref adv) = s.advanced {
                        207.0 - 1.015 * (s.word_count as f64 / adv.sentence_count as f64)
                            - 73.6 * (s.syllable_count as f64 / s.word_count as f64)
                    } else {
                        unreachable!();
                    }})),
            ),
            Language::German_1996 => (
                TrainingData::german(),
                Some(Box::new(|s: &ChapterStats| {
                    if let Some(ref adv) = s.advanced {
                        180.0 - (s.word_count as f64 / adv.sentence_count as f64)
                            - 84.6 * (s.syllable_count as f64 / s.word_count as f64)
                    } else {
                        unreachable!();
                    }
                })),
            ),
            Language::Greek_Poly => (TrainingData::greek(), None),
            Language::Italian => (
                TrainingData::italian(),
                Some(Box::new(|s: &ChapterStats| {
                    if let Some(ref adv) = s.advanced {
                        217.0 - 1.3 * (s.word_count as f64 / adv.sentence_count as f64)
                            - 60.0 * (s.syllable_count as f64 * 100.0 / s.word_count as f64)
                    } else {
                        unreachable!();
                    }
                })),
            ),
            Language::Norwegian_Bokmal => (TrainingData::norwegian(), None),
            Language::Polish => (TrainingData::polish(), None),
            Language::Portuguese => (TrainingData::portuguese(), None),
            Language::Slovenian => (TrainingData::slovene(), None),
            Language::Spanish => (
                TrainingData::spanish(),
                Some(Box::new(|s: &ChapterStats| {
                    if let Some(ref adv) = s.advanced {
                        206.84 - 1.02 * (s.word_count as f64 / adv.sentence_count as f64)
                        - 60.0 * (s.syllable_count as f64 * 100.0 / s.word_count as f64)
                    } else {
                        unreachable!();
                    }})),
            ),
            Language::Swedish => (TrainingData::swedish(), None),
            Language::Turkish => (TrainingData::turkish(), None),
            _ => {
                unreachable!("language_data should handle all casess returned by language_from_str");
            },
        }
    }

    fn flesch_text(score: f64) -> String {
        String::from(match score {
            s if s.is_nan() => "Not availabe",
            s if s < 30.0 => "Very difficult",
            s if s < 50.0 => "Difficult",
            s if s < 60.0 => "Fairly difficult",
            s if s < 70.0 => "Standard",
            s if s < 80.0 => "Fairly easy",
            s if s < 90.0 => "Easy",
            _ => "Very Easy",
        })
    }
}

impl fmt::Display for Stats {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let max_chapter_length = self.chapters
            .iter()
            .max_by_key(|e| e.name.chars().count())
            .unwrap()
            .name
            .chars()
            .count() + 3;
        if self.advanced {
            write!(
                f,
                "{:<width$} {:>8} {:>10} {:>7} {:>11} {:>11} {:>16} {:>29}\n---------\n",
                style::header(&lformat!("Chapter")),
                style::header(&lformat!("Chars")),
                style::header(&lformat!("Syllables")),
                style::header(&lformat!("Words")),
                style::header(&lformat!("Sentences")),
                style::header(&lformat!("Chars/Word")),
                style::header(&lformat!("Words/Sentence")),
                style::header(&lformat!("Flesch reading ease index")),
                width = max_chapter_length
            )?;
        } else {
            write!(
                f,
                "{:<width$} {:>8} {:>10} {:>11}\n---------\n",
                style::header(&lformat!("Chapter")),
                style::header(&lformat!("Chars")),
                style::header(&lformat!("Words")),
                style::header(&lformat!("Chars/Word")),
                width = max_chapter_length
            )?;
        }
        for c in &self.chapters {
            if let Some(ref adv) = c.advanced {
                write!(
                    f,
                    "{:<width$} {:>8} {:>10} {:>7} {:>11} {:>11.2} {:>16.2} {:>8.1} => {:>17}\n",
                    style::element(&c.name),
                    c.char_count,
                    c.syllable_count,
                    c.word_count,
                    adv.sentence_count,
                    c.char_count as f64 / c.word_count as f64,
                    c.word_count as f64 / adv.sentence_count as f64,
                    adv.flesch_score,
                    Self::flesch_text(adv.flesch_score),
                    width = max_chapter_length
                )?;
            } else {
                write!(
                    f,
                    "{:<width$} {:>8} {:>10} {:>11.2}\n",
                    style::element(&c.name),
                    c.char_count,
                    c.word_count,
                    c.char_count as f64 / c.word_count as f64,
                    width = max_chapter_length
                )?;
            }
        }
        let total = self.chapters.iter().fold((0, 0, 0, 0, 0.0, 0), |acc, c| {
            let sentence_count;
            let flesch_score;
            if let Some(ref adv) = c.advanced {
                sentence_count = adv.sentence_count;
                flesch_score = adv.flesch_score;
            } else {
                sentence_count = 0;
                flesch_score = f64::NAN;
            };
            (
                acc.0 + c.char_count,
                acc.1 + c.syllable_count,
                acc.2 + c.word_count,
                acc.3 + sentence_count,
                acc.4 + flesch_score,
                acc.5 + 1,
            )
        });
        if self.advanced {
            write!(
                f,
                "---------\n{:<width$} {:>8} {:>10} {:>7} {:>11} {:>11.2} {:>16.2} {:>8.1} => {:>17}\n",
                style::element(&lformat!("TOTAL:")),
                total.0,
                total.1,
                total.2,
                total.3,
                total.0 as f64 / total.2 as f64,
                total.2 as f64 / total.3 as f64,
                total.4 / total.5 as f64,
                Self::flesch_text(total.4 / total.5 as f64),
                width = max_chapter_length
            )
        } else {
            write!(
                f,
                "---------\n{:<width$} {:>8} {:>10} {:>11.2}\n",
                style::element(&lformat!("TOTAL:")),
                total.0,
                total.2,
                total.0 as f64 / total.2 as f64,
                width = max_chapter_length
            )
        }
    }
}
<|endoftext|>


<commit_before>// This file is part of the uutils coreutils package.
//
// (c) Joseph Crail <jbcrail@gmail.com>
// (c) Jian Zeng <anonymousknight96 AT gmail.com>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
//

#[cfg(unix)]
use super::libc;
use std::env;
use std::fs;
use std::io::{Error, ErrorKind};
use std::io::Result as IOResult;
use std::path::{Component, Path, PathBuf};
use std::borrow::Cow;

#[cfg(unix)]
pub fn resolve_relative_path<'a>(path: &'a Path) -> Cow<'a, Path> {
    if path.is_absolute() {
        return path.into();
    }
    let mut result = env::current_dir().unwrap_or(PathBuf::from("/"));
    for comp in path.components() {
        match comp {
            Component::ParentDir => {
                result.pop();
            }
            Component::CurDir => (),
            Component::Normal(s) => result.push(s),
            _ => unreachable!(),
        }
    }
    result.into()
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CanonicalizeMode {
    None,
    Normal,
    Existing,
    Missing,
}

fn resolve<P: AsRef<Path>>(original: P) -> IOResult<PathBuf> {
    const MAX_LINKS_FOLLOWED: u32 = 255;
    let mut followed = 0;
    let mut result = original.as_ref().to_path_buf();
    loop {
        if followed == MAX_LINKS_FOLLOWED {
            return Err(Error::new(ErrorKind::InvalidInput, "maximum links followed"));
        }

        match fs::symlink_metadata(&result) {
            Err(e) => return Err(e),
            Ok(ref m) if !m.file_type().is_symlink() => break,
            Ok(..) => {
                followed += 1;
                match fs::read_link(&result) {
                    Ok(path) => {
                        result.pop();
                        result.push(path);
                    }
                    Err(e) => {
                        return Err(e);
                    }
                }
            }
        }
    }
    Ok(result)
}

pub fn canonicalize<P: AsRef<Path>>(original: P, can_mode: CanonicalizeMode) -> IOResult<PathBuf> {
    // Create an absolute path
    let original = original.as_ref();
    let original = if original.is_absolute() {
        original.to_path_buf()
    } else {
        env::current_dir().unwrap().join(original)
    };

    let mut result = PathBuf::new();
    let mut parts = vec![];

    // Split path by directory separator; add prefix (Windows-only) and root
    // directory to final path buffer; add remaining parts to temporary
    // vector for canonicalization.
    for part in original.components() {
        match part {
            Component::Prefix(_) |
            Component::RootDir => {
                result.push(part.as_os_str());
            }
            Component::CurDir => (),
            Component::ParentDir => {
                parts.pop();
            }
            Component::Normal(_) => {
                parts.push(part.as_os_str());
            }
        }
    }

    // Resolve the symlinks where possible
    if !parts.is_empty() {
        for part in parts[..parts.len() - 1].iter() {
            result.push(part);

            if can_mode == CanonicalizeMode::None {
                continue;
            }

            match resolve(&result) {
                Err(e) => {
                    match can_mode {
                        CanonicalizeMode::Missing => continue,
                        _ => return Err(e),
                    }
                }
                Ok(path) => {
                    result.pop();
                    result.push(path);
                }
            }
        }

        result.push(parts.last().unwrap());

        match resolve(&result) {
            Err(e) => {
                if can_mode == CanonicalizeMode::Existing {
                    return Err(e);
                }
            }
            Ok(path) => {
                result.pop();
                result.push(path);
            }
        }
    }
    Ok(result)
}

#[cfg(unix)]
pub fn is_stdin_interactive() -> bool {
    unsafe { libc::isatty(libc::STDIN_FILENO) == 1 }
}

#[cfg(windows)]
pub fn is_stdin_interactive() -> bool {
    false
}

#[cfg(unix)]
pub fn is_stdout_interactive() -> bool {
    unsafe { libc::isatty(libc::STDOUT_FILENO) == 1 }
}

#[cfg(windows)]
pub fn is_stdout_interactive() -> bool {
    false
}

#[cfg(unix)]
pub fn is_stderr_interactive() -> bool {
    unsafe { libc::isatty(libc::STDERR_FILENO) == 1 }
}

#[cfg(windows)]
pub fn is_stderr_interactive() -> bool {
    false
}
<commit_msg>chown,chgrp: fix bug in option --preserve-root<commit_after>// This file is part of the uutils coreutils package.
//
// (c) Joseph Crail <jbcrail@gmail.com>
// (c) Jian Zeng <anonymousknight96 AT gmail.com>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
//

#[cfg(unix)]
use super::libc;
use std::env;
use std::fs;
use std::io::{Error, ErrorKind};
use std::io::Result as IOResult;
use std::path::{Component, Path, PathBuf};
use std::borrow::Cow;

pub fn resolve_relative_path<'a>(path: &'a Path) -> Cow<'a, Path> {
    if path.components().all(|e| e != Component::ParentDir) {
        return path.into();
    }
    let root = Component::RootDir.as_os_str();
    let mut result = env::current_dir().unwrap_or(PathBuf::from(root));
    for comp in path.components() {
        match comp {
            Component::ParentDir => {
                result.pop();
            }
            Component::CurDir => (),
            Component::RootDir |
            Component::Normal(_) |
            Component::Prefix(_) => result.push(comp.as_os_str()),
        }
    }
    result.into()
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CanonicalizeMode {
    None,
    Normal,
    Existing,
    Missing,
}

fn resolve<P: AsRef<Path>>(original: P) -> IOResult<PathBuf> {
    const MAX_LINKS_FOLLOWED: u32 = 255;
    let mut followed = 0;
    let mut result = original.as_ref().to_path_buf();
    loop {
        if followed == MAX_LINKS_FOLLOWED {
            return Err(Error::new(ErrorKind::InvalidInput, "maximum links followed"));
        }

        match fs::symlink_metadata(&result) {
            Err(e) => return Err(e),
            Ok(ref m) if !m.file_type().is_symlink() => break,
            Ok(..) => {
                followed += 1;
                match fs::read_link(&result) {
                    Ok(path) => {
                        result.pop();
                        result.push(path);
                    }
                    Err(e) => {
                        return Err(e);
                    }
                }
            }
        }
    }
    Ok(result)
}

pub fn canonicalize<P: AsRef<Path>>(original: P, can_mode: CanonicalizeMode) -> IOResult<PathBuf> {
    // Create an absolute path
    let original = original.as_ref();
    let original = if original.is_absolute() {
        original.to_path_buf()
    } else {
        env::current_dir().unwrap().join(original)
    };

    let mut result = PathBuf::new();
    let mut parts = vec![];

    // Split path by directory separator; add prefix (Windows-only) and root
    // directory to final path buffer; add remaining parts to temporary
    // vector for canonicalization.
    for part in original.components() {
        match part {
            Component::Prefix(_) |
            Component::RootDir => {
                result.push(part.as_os_str());
            }
            Component::CurDir => (),
            Component::ParentDir => {
                parts.pop();
            }
            Component::Normal(_) => {
                parts.push(part.as_os_str());
            }
        }
    }

    // Resolve the symlinks where possible
    if !parts.is_empty() {
        for part in parts[..parts.len() - 1].iter() {
            result.push(part);

            if can_mode == CanonicalizeMode::None {
                continue;
            }

            match resolve(&result) {
                Err(e) => {
                    match can_mode {
                        CanonicalizeMode::Missing => continue,
                        _ => return Err(e),
                    }
                }
                Ok(path) => {
                    result.pop();
                    result.push(path);
                }
            }
        }

        result.push(parts.last().unwrap());

        match resolve(&result) {
            Err(e) => {
                if can_mode == CanonicalizeMode::Existing {
                    return Err(e);
                }
            }
            Ok(path) => {
                result.pop();
                result.push(path);
            }
        }
    }
    Ok(result)
}

#[cfg(unix)]
pub fn is_stdin_interactive() -> bool {
    unsafe { libc::isatty(libc::STDIN_FILENO) == 1 }
}

#[cfg(windows)]
pub fn is_stdin_interactive() -> bool {
    false
}

#[cfg(unix)]
pub fn is_stdout_interactive() -> bool {
    unsafe { libc::isatty(libc::STDOUT_FILENO) == 1 }
}

#[cfg(windows)]
pub fn is_stdout_interactive() -> bool {
    false
}

#[cfg(unix)]
pub fn is_stderr_interactive() -> bool {
    unsafe { libc::isatty(libc::STDERR_FILENO) == 1 }
}

#[cfg(windows)]
pub fn is_stderr_interactive() -> bool {
    false
}
<|endoftext|>


<commit_before>use proc_macro::TokenStream;
use quote::Tokens;
use syn;

use logging_enabled;

pub fn ber_alias_serialize(ast: syn::MacroInput) -> TokenStream {
  let name = &ast.ident;
  let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
  let logging = logging_enabled(&ast);

  let implicit_msg = if logging {
    quote!(debug!("Skipping encoding of implicit tag");)
  } else {
    Tokens::new()
  };

  let expanded = quote! {
    #ast

    impl #impl_generics ::asn1_cereal::BerSerialize for #name #ty_generics #where_clause {
      fn _serialize_enc<E: ::asn1_cereal::BerEncRules, W: std::io::Write>
          (&self, e: E, writer: &mut W) -> Option<Result<(), ::asn1_cereal::err::EncodeError>> {
        let tag = <Self as ::asn1_cereal::Asn1Info>::asn1_tag();
        // If we have a tag, and encoding uses implicit tags, skip our tag.
        if tag.is_some() && E::tag_rules() == ::asn1_cereal::ber::enc::TagEnc::Implicit {
          #implicit_msg
          Some(self.serialize_value(e, writer))
        } else {
          None
        }
      }

      fn serialize_value<E: ::asn1_cereal::BerEncRules, W: std::io::Write>
          (&self, e: E, writer: &mut W) -> Result<(), ::asn1_cereal::err::EncodeError> {
        self.0.serialize_enc(e, writer)
      }
    }
  };
  expanded.to_string().parse().expect("Failure parsing derived impl")
}

pub fn ber_alias_deserialize(ast: syn::MacroInput) -> TokenStream {
  let name = &ast.ident;
  let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
  let logging = logging_enabled(&ast);

  let no_tag_msg = if logging {
    quote!(debug!("Skipping decoding of empty tag");)
  } else {
    Tokens::new()
  };

  let implicit_msg = if logging {
    quote!(debug!("Skipping decoding of implicit tag");)
  } else {
    Tokens::new()
  };

  let expanded = quote! {
    #ast

    impl #impl_generics ::asn1_cereal::BerDeserialize for #name #ty_generics #where_clause {
      fn _deserialize_with_tag<E: ::asn1_cereal::BerEncRules, I: Iterator<Item=std::io::Result<u8>>>
          (e: E, reader: &mut I, tag: ::asn1_cereal::tag::Tag, len: ::asn1_cereal::tag::Len)
          -> Option<Result<Self, ::asn1_cereal::err::DecodeError>> {
        // If we don't have a tag, decode the internal item.
        let my_tag = match <Self as ::asn1_cereal::Asn1Info>::asn1_tag() {
          Some(tag) => tag,
          None => {
            #no_tag_msg
            return Some(
              ::asn1_cereal::BerDeserialize::deserialize_with_tag(e, reader, tag, len)
              .and_then(|s| Ok(#name(s)))
            )
          },
        };

        // If we're decoding using Implicit tagging rules, throw an error if this isn't an implicit tag.
        if E::tag_rules() == ::asn1_cereal::ber::enc::TagEnc::Implicit && tag == my_tag {
          return Some(Err(::asn1_cereal::err::DecodeError::ExplicitTag));
        }

        if tag != my_tag {
          #implicit_msg
          let res =
            ::asn1_cereal::BerDeserialize::deserialize_with_tag(e, reader, tag, len)
            .and_then(|s| Ok(#name(s)));
          Some(res)
        } else {
          None
        }
      }

      fn deserialize_value<E: ::asn1_cereal::BerEncRules, I: Iterator<Item=std::io::Result<u8>>>
          (e: E, reader: &mut I, _len: ::asn1_cereal::tag::Len) -> Result<Self, ::asn1_cereal::err::DecodeError> {
        Ok(#name(try!(::asn1_cereal::BerDeserialize::deserialize_enc(e, reader))))
      }
    }
  };
  expanded.to_string().parse().expect("Failure parsing derived impl")
}
<commit_msg>Refactor logging conditionals<commit_after>use proc_macro::TokenStream;
use quote::Tokens;
use syn;

use logging_enabled;

pub fn ber_alias_serialize(ast: syn::MacroInput) -> TokenStream {
  let name = &ast.ident;
  let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
  let logging = logging_enabled(&ast);

  let mut implicit_msg = Tokens::new();

  if logging {
    implicit_msg = quote!(debug!("Skipping encoding of implicit tag");)
  }

  let expanded = quote! {
    #ast

    impl #impl_generics ::asn1_cereal::BerSerialize for #name #ty_generics #where_clause {
      fn _serialize_enc<E: ::asn1_cereal::BerEncRules, W: std::io::Write>
          (&self, e: E, writer: &mut W) -> Option<Result<(), ::asn1_cereal::err::EncodeError>> {
        let tag = <Self as ::asn1_cereal::Asn1Info>::asn1_tag();
        // If we have a tag, and encoding uses implicit tags, skip our tag.
        if tag.is_some() && E::tag_rules() == ::asn1_cereal::ber::enc::TagEnc::Implicit {
          #implicit_msg
          Some(self.serialize_value(e, writer))
        } else {
          None
        }
      }

      fn serialize_value<E: ::asn1_cereal::BerEncRules, W: std::io::Write>
          (&self, e: E, writer: &mut W) -> Result<(), ::asn1_cereal::err::EncodeError> {
        self.0.serialize_enc(e, writer)
      }
    }
  };
  expanded.to_string().parse().expect("Failure parsing derived impl")
}

pub fn ber_alias_deserialize(ast: syn::MacroInput) -> TokenStream {
  let name = &ast.ident;
  let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
  let logging = logging_enabled(&ast);

  let mut no_tag_msg = Tokens::new();
  let mut implicit_msg = Tokens::new();

  if logging {
    no_tag_msg = quote!(debug!("Skipping decoding of empty tag"););
    implicit_msg = quote!(debug!("Skipping decoding of implicit tag"););
  }

  let expanded = quote! {
    #ast

    impl #impl_generics ::asn1_cereal::BerDeserialize for #name #ty_generics #where_clause {
      fn _deserialize_with_tag<E: ::asn1_cereal::BerEncRules, I: Iterator<Item=std::io::Result<u8>>>
          (e: E, reader: &mut I, tag: ::asn1_cereal::tag::Tag, len: ::asn1_cereal::tag::Len)
          -> Option<Result<Self, ::asn1_cereal::err::DecodeError>> {
        // If we don't have a tag, decode the internal item.
        let my_tag = match <Self as ::asn1_cereal::Asn1Info>::asn1_tag() {
          Some(tag) => tag,
          None => {
            #no_tag_msg
            return Some(
              ::asn1_cereal::BerDeserialize::deserialize_with_tag(e, reader, tag, len)
              .and_then(|s| Ok(#name(s)))
            )
          },
        };

        // If we're decoding using Implicit tagging rules, throw an error if this isn't an implicit tag.
        if E::tag_rules() == ::asn1_cereal::ber::enc::TagEnc::Implicit && tag == my_tag {
          return Some(Err(::asn1_cereal::err::DecodeError::ExplicitTag));
        }

        if tag != my_tag {
          #implicit_msg
          let res =
            ::asn1_cereal::BerDeserialize::deserialize_with_tag(e, reader, tag, len)
            .and_then(|s| Ok(#name(s)));
          Some(res)
        } else {
          None
        }
      }

      fn deserialize_value<E: ::asn1_cereal::BerEncRules, I: Iterator<Item=std::io::Result<u8>>>
          (e: E, reader: &mut I, _len: ::asn1_cereal::tag::Len) -> Result<Self, ::asn1_cereal::err::DecodeError> {
        Ok(#name(try!(::asn1_cereal::BerDeserialize::deserialize_enc(e, reader))))
      }
    }
  };
  expanded.to_string().parse().expect("Failure parsing derived impl")
}
<|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 https://mozilla.org/MPL/2.0/. */

use app_units::Au;
use gfx::font::{
    fallback_font_families, FontDescriptor, FontFamilyDescriptor, FontFamilyName, FontSearchScope,
};
use gfx::font_cache_thread::{FontTemplateInfo, FontTemplates};
use gfx::font_context::{FontContext, FontContextHandle, FontSource};
use gfx::font_template::FontTemplateDescriptor;
use servo_arc::Arc;
use servo_atoms::Atom;
use std::cell::Cell;
use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use std::path::PathBuf;
use std::rc::Rc;
use style::properties::longhands::font_variant_caps::computed_value::T as FontVariantCaps;
use style::properties::style_structs::Font as FontStyleStruct;
use style::values::computed::font::{
    FamilyName, FamilyNameSyntax, FontFamily, FontFamilyList, FontSize,
};
use style::values::computed::font::{FontStretch, FontWeight, SingleFontFamily};
use style::values::generics::font::FontStyle;

struct TestFontSource {
    handle: FontContextHandle,
    families: HashMap<String, FontTemplates>,
    find_font_count: Rc<Cell<isize>>,
}

impl TestFontSource {
    fn new() -> TestFontSource {
        let mut csstest_ascii = FontTemplates::new();
        Self::add_face(&mut csstest_ascii, "csstest-ascii", None);

        let mut csstest_basic = FontTemplates::new();
        Self::add_face(&mut csstest_basic, "csstest-basic-regular", None);

        let mut fallback = FontTemplates::new();
        Self::add_face(&mut fallback, "csstest-basic-regular", Some("fallback"));

        let mut families = HashMap::new();
        families.insert("CSSTest ASCII".to_owned(), csstest_ascii);
        families.insert("CSSTest Basic".to_owned(), csstest_basic);
        families.insert(fallback_font_families(None)[0].to_owned(), fallback);

        TestFontSource {
            handle: FontContextHandle::new(),
            families,
            find_font_count: Rc::new(Cell::new(0)),
        }
    }

    fn add_face(family: &mut FontTemplates, name: &str, identifier: Option<&str>) {
        let mut path: PathBuf = [env!("CARGO_MANIFEST_DIR"), "tests", "support", "CSSTest"]
            .iter()
            .collect();
        path.push(format!("{}.ttf", name));

        let file = File::open(path).unwrap();
        let identifier = Atom::from(identifier.unwrap_or(name));

        family.add_template(identifier, Some(file.bytes().map(|b| b.unwrap()).collect()))
    }
}

impl FontSource for TestFontSource {
    fn get_font_instance(
        &mut self,
        _key: webrender_api::FontKey,
        _size: Au,
    ) -> webrender_api::FontInstanceKey {
        webrender_api::FontInstanceKey(webrender_api::IdNamespace(0), 0)
    }

    fn font_template(
        &mut self,
        template_descriptor: FontTemplateDescriptor,
        family_descriptor: FontFamilyDescriptor,
    ) -> Option<FontTemplateInfo> {
        let handle = &self.handle;

        self.find_font_count.set(self.find_font_count.get() + 1);
        self.families
            .get_mut(family_descriptor.name())
            .and_then(|family| family.find_font_for_style(&template_descriptor, handle))
            .map(|template| FontTemplateInfo {
                font_template: template,
                font_key: webrender_api::FontKey(webrender_api::IdNamespace(0), 0),
            })
    }
}

fn style() -> FontStyleStruct {
    let mut style = FontStyleStruct {
        font_family: FontFamily::serif(),
        font_style: FontStyle::Normal,
        font_variant_caps: FontVariantCaps::Normal,
        font_weight: FontWeight::normal(),
        font_size: FontSize::medium(),
        font_stretch: FontStretch::hundred(),
        hash: 0,
    };
    style.compute_font_hash();
    style
}

fn font_family(names: Vec<&str>) -> FontFamily {
    let names: Vec<SingleFontFamily> = names
        .into_iter()
        .map(|name| {
            SingleFontFamily::FamilyName(FamilyName {
                name: Atom::from(name),
                syntax: FamilyNameSyntax::Quoted,
            })
        })
        .collect();

    FontFamily {
        families: FontFamilyList::new(names.into_boxed_slice()),
        is_system_font: false,
    }
}

#[test]
fn test_font_group_is_cached_by_style() {
    let source = TestFontSource::new();
    let mut context = FontContext::new(source);

    let style1 = style();

    let mut style2 = style();
    style2.set_font_style(FontStyle::Italic);

    assert_eq!(
        context.font_group(Arc::new(style1.clone())).as_ptr(),
        context.font_group(Arc::new(style1.clone())).as_ptr(),
        "the same font group should be returned for two styles with the same hash"
    );

    assert_ne!(
        context.font_group(Arc::new(style1.clone())).as_ptr(),
        context.font_group(Arc::new(style2.clone())).as_ptr(),
        "different font groups should be returned for two styles with different hashes"
    )
}

#[test]
fn test_font_group_find_by_codepoint() {
    let source = TestFontSource::new();
    let count = source.find_font_count.clone();
    let mut context = FontContext::new(source);

    let mut style = style();
    style.set_font_family(font_family(vec!["CSSTest ASCII", "CSSTest Basic"]));

    let group = context.font_group(Arc::new(style));

    let font = group
        .borrow_mut()
        .find_by_codepoint(&mut context, 'a')
        .unwrap();
    assert_eq!(&*font.borrow().identifier(), "csstest-ascii");
    assert_eq!(
        count.get(),
        1,
        "only the first font in the list should have been loaded"
    );

    let font = group
        .borrow_mut()
        .find_by_codepoint(&mut context, 'a')
        .unwrap();
    assert_eq!(&*font.borrow().identifier(), "csstest-ascii");
    assert_eq!(
        count.get(),
        1,
        "we shouldn't load the same font a second time"
    );

    let font = group
        .borrow_mut()
        .find_by_codepoint(&mut context, 'á')
        .unwrap();
    assert_eq!(&*font.borrow().identifier(), "csstest-basic-regular");
    assert_eq!(count.get(), 2, "both fonts should now have been loaded");
}

#[test]
fn test_font_fallback() {
    let source = TestFontSource::new();
    let mut context = FontContext::new(source);

    let mut style = style();
    style.set_font_family(font_family(vec!["CSSTest ASCII"]));

    let group = context.font_group(Arc::new(style));

    let font = group
        .borrow_mut()
        .find_by_codepoint(&mut context, 'a')
        .unwrap();
    assert_eq!(
        &*font.borrow().identifier(),
        "csstest-ascii",
        "a family in the group should be used if there is a matching glyph"
    );

    let font = group
        .borrow_mut()
        .find_by_codepoint(&mut context, 'á')
        .unwrap();
    assert_eq!(
        &*font.borrow().identifier(),
        "fallback",
        "a fallback font should be used if there is no matching glyph in the group"
    );
}

#[test]
fn test_font_template_is_cached() {
    let source = TestFontSource::new();
    let count = source.find_font_count.clone();
    let mut context = FontContext::new(source);

    let mut font_descriptor = FontDescriptor {
        template_descriptor: FontTemplateDescriptor {
            weight: FontWeight::normal(),
            stretch: FontStretch::hundred(),
            style: FontStyle::Normal,
        },
        variant: FontVariantCaps::Normal,
        pt_size: Au(10),
    };

    let family_descriptor =
        FontFamilyDescriptor::new(FontFamilyName::from("CSSTest Basic"), FontSearchScope::Any);

    let font1 = context.font(&font_descriptor, &family_descriptor).unwrap();

    font_descriptor.pt_size = Au(20);
    let font2 = context.font(&font_descriptor, &family_descriptor).unwrap();

    assert_ne!(
        font1.borrow().actual_pt_size,
        font2.borrow().actual_pt_size,
        "the same font should not have been returned"
    );

    assert_eq!(
        count.get(),
        1,
        "we should only have fetched the template data from the cache thread once"
    );
}
<commit_msg>Fix gfx unit tests.<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 https://mozilla.org/MPL/2.0/. */

use app_units::Au;
use gfx::font::{
    fallback_font_families, FontDescriptor, FontFamilyDescriptor, FontFamilyName, FontSearchScope,
};
use gfx::font_cache_thread::{FontTemplateInfo, FontTemplates};
use gfx::font_context::{FontContext, FontContextHandle, FontSource};
use gfx::font_template::FontTemplateDescriptor;
use servo_arc::Arc;
use servo_atoms::Atom;
use std::cell::Cell;
use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use std::path::PathBuf;
use std::rc::Rc;
use style::properties::longhands::font_variant_caps::computed_value::T as FontVariantCaps;
use style::properties::style_structs::Font as FontStyleStruct;
use style::values::computed::font::{
    FamilyName, FontFamily, FontFamilyList, FontFamilyNameSyntax, FontSize,
};
use style::values::computed::font::{FontStretch, FontWeight, SingleFontFamily};
use style::values::generics::font::FontStyle;

struct TestFontSource {
    handle: FontContextHandle,
    families: HashMap<String, FontTemplates>,
    find_font_count: Rc<Cell<isize>>,
}

impl TestFontSource {
    fn new() -> TestFontSource {
        let mut csstest_ascii = FontTemplates::new();
        Self::add_face(&mut csstest_ascii, "csstest-ascii", None);

        let mut csstest_basic = FontTemplates::new();
        Self::add_face(&mut csstest_basic, "csstest-basic-regular", None);

        let mut fallback = FontTemplates::new();
        Self::add_face(&mut fallback, "csstest-basic-regular", Some("fallback"));

        let mut families = HashMap::new();
        families.insert("CSSTest ASCII".to_owned(), csstest_ascii);
        families.insert("CSSTest Basic".to_owned(), csstest_basic);
        families.insert(fallback_font_families(None)[0].to_owned(), fallback);

        TestFontSource {
            handle: FontContextHandle::new(),
            families,
            find_font_count: Rc::new(Cell::new(0)),
        }
    }

    fn add_face(family: &mut FontTemplates, name: &str, identifier: Option<&str>) {
        let mut path: PathBuf = [env!("CARGO_MANIFEST_DIR"), "tests", "support", "CSSTest"]
            .iter()
            .collect();
        path.push(format!("{}.ttf", name));

        let file = File::open(path).unwrap();
        let identifier = Atom::from(identifier.unwrap_or(name));

        family.add_template(identifier, Some(file.bytes().map(|b| b.unwrap()).collect()))
    }
}

impl FontSource for TestFontSource {
    fn get_font_instance(
        &mut self,
        _key: webrender_api::FontKey,
        _size: Au,
    ) -> webrender_api::FontInstanceKey {
        webrender_api::FontInstanceKey(webrender_api::IdNamespace(0), 0)
    }

    fn font_template(
        &mut self,
        template_descriptor: FontTemplateDescriptor,
        family_descriptor: FontFamilyDescriptor,
    ) -> Option<FontTemplateInfo> {
        let handle = &self.handle;

        self.find_font_count.set(self.find_font_count.get() + 1);
        self.families
            .get_mut(family_descriptor.name())
            .and_then(|family| family.find_font_for_style(&template_descriptor, handle))
            .map(|template| FontTemplateInfo {
                font_template: template,
                font_key: webrender_api::FontKey(webrender_api::IdNamespace(0), 0),
            })
    }
}

fn style() -> FontStyleStruct {
    let mut style = FontStyleStruct {
        font_family: FontFamily::serif(),
        font_style: FontStyle::Normal,
        font_variant_caps: FontVariantCaps::Normal,
        font_weight: FontWeight::normal(),
        font_size: FontSize::medium(),
        font_stretch: FontStretch::hundred(),
        hash: 0,
    };
    style.compute_font_hash();
    style
}

fn font_family(names: Vec<&str>) -> FontFamily {
    let names: Vec<SingleFontFamily> = names
        .into_iter()
        .map(|name| {
            SingleFontFamily::FamilyName(FamilyName {
                name: Atom::from(name),
                syntax: FontFamilyNameSyntax::Quoted,
            })
        })
        .collect();

    FontFamily {
        families: FontFamilyList::new(names.into_boxed_slice()),
        is_system_font: false,
    }
}

#[test]
fn test_font_group_is_cached_by_style() {
    let source = TestFontSource::new();
    let mut context = FontContext::new(source);

    let style1 = style();

    let mut style2 = style();
    style2.set_font_style(FontStyle::Italic);

    assert_eq!(
        context.font_group(Arc::new(style1.clone())).as_ptr(),
        context.font_group(Arc::new(style1.clone())).as_ptr(),
        "the same font group should be returned for two styles with the same hash"
    );

    assert_ne!(
        context.font_group(Arc::new(style1.clone())).as_ptr(),
        context.font_group(Arc::new(style2.clone())).as_ptr(),
        "different font groups should be returned for two styles with different hashes"
    )
}

#[test]
fn test_font_group_find_by_codepoint() {
    let source = TestFontSource::new();
    let count = source.find_font_count.clone();
    let mut context = FontContext::new(source);

    let mut style = style();
    style.set_font_family(font_family(vec!["CSSTest ASCII", "CSSTest Basic"]));

    let group = context.font_group(Arc::new(style));

    let font = group
        .borrow_mut()
        .find_by_codepoint(&mut context, 'a')
        .unwrap();
    assert_eq!(&*font.borrow().identifier(), "csstest-ascii");
    assert_eq!(
        count.get(),
        1,
        "only the first font in the list should have been loaded"
    );

    let font = group
        .borrow_mut()
        .find_by_codepoint(&mut context, 'a')
        .unwrap();
    assert_eq!(&*font.borrow().identifier(), "csstest-ascii");
    assert_eq!(
        count.get(),
        1,
        "we shouldn't load the same font a second time"
    );

    let font = group
        .borrow_mut()
        .find_by_codepoint(&mut context, 'á')
        .unwrap();
    assert_eq!(&*font.borrow().identifier(), "csstest-basic-regular");
    assert_eq!(count.get(), 2, "both fonts should now have been loaded");
}

#[test]
fn test_font_fallback() {
    let source = TestFontSource::new();
    let mut context = FontContext::new(source);

    let mut style = style();
    style.set_font_family(font_family(vec!["CSSTest ASCII"]));

    let group = context.font_group(Arc::new(style));

    let font = group
        .borrow_mut()
        .find_by_codepoint(&mut context, 'a')
        .unwrap();
    assert_eq!(
        &*font.borrow().identifier(),
        "csstest-ascii",
        "a family in the group should be used if there is a matching glyph"
    );

    let font = group
        .borrow_mut()
        .find_by_codepoint(&mut context, 'á')
        .unwrap();
    assert_eq!(
        &*font.borrow().identifier(),
        "fallback",
        "a fallback font should be used if there is no matching glyph in the group"
    );
}

#[test]
fn test_font_template_is_cached() {
    let source = TestFontSource::new();
    let count = source.find_font_count.clone();
    let mut context = FontContext::new(source);

    let mut font_descriptor = FontDescriptor {
        template_descriptor: FontTemplateDescriptor {
            weight: FontWeight::normal(),
            stretch: FontStretch::hundred(),
            style: FontStyle::Normal,
        },
        variant: FontVariantCaps::Normal,
        pt_size: Au(10),
    };

    let family_descriptor =
        FontFamilyDescriptor::new(FontFamilyName::from("CSSTest Basic"), FontSearchScope::Any);

    let font1 = context.font(&font_descriptor, &family_descriptor).unwrap();

    font_descriptor.pt_size = Au(20);
    let font2 = context.font(&font_descriptor, &family_descriptor).unwrap();

    assert_ne!(
        font1.borrow().actual_pt_size,
        font2.borrow().actual_pt_size,
        "the same font should not have been returned"
    );

    assert_eq!(
        count.get(),
        1,
        "we should only have fetched the template data from the cache thread once"
    );
}
<|endoftext|>