diff --git a/README.md b/README.md index 6c3b09f..0a19ead 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,6 @@ This project contains a fully fledged parser for Brother's FCM format, written in Rust using Nom. -## Roadmap - -- Writing FCM files - ## License > This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. diff --git a/samples/test/square_roundtrip.fcm b/samples/test/square_roundtrip.fcm new file mode 100644 index 0000000..c7701e3 Binary files /dev/null and b/samples/test/square_roundtrip.fcm differ diff --git a/src/alignment_data.rs b/src/alignment_data.rs new file mode 100644 index 0000000..c03e2de --- /dev/null +++ b/src/alignment_data.rs @@ -0,0 +1,33 @@ +use crate::encode::Encode; +use crate::point; +use crate::point::Point; +use crate::util::bool32; +use nom::combinator::map; +use nom::multi::length_count; +use nom::number::complete::le_u32; +use nom::sequence::tuple; +use nom::IResult; + +#[derive(Debug)] +pub struct AlignmentData { + pub needed: bool, + pub marks: Vec, +} + +pub(crate) fn read_alignment_data(input: &[u8]) -> IResult<&[u8], AlignmentData> { + map( + tuple((bool32, length_count(le_u32, point::read_point))), + |(needed, marks)| AlignmentData { needed, marks }, + )(input) +} + +impl Encode for AlignmentData { + fn encode(&self, buffer: &mut Vec) -> std::io::Result<()> { + (self.needed as u32).encode(buffer)?; + (self.marks.len() as u32).encode(buffer)?; + for mark in &self.marks { + mark.encode(buffer)?; + } + Ok(()) + } +} diff --git a/src/cut_data.rs b/src/cut_data.rs index 35aed24..b0ec21e 100644 --- a/src/cut_data.rs +++ b/src/cut_data.rs @@ -1,13 +1,12 @@ use nom::combinator::{cond, flat_map, map}; -use nom::multi::length_count; use nom::number::complete::le_u32; use nom::sequence::tuple; use nom::IResult; +use crate::alignment_data::{read_alignment_data, AlignmentData}; +use crate::encode::Encode; +use crate::file_type; use crate::file_type::FileType; -use crate::point::Point; -use crate::util::bool32; -use crate::{file_type, point}; #[derive(Debug)] pub struct CutData { @@ -16,8 +15,7 @@ pub struct CutData { pub cut_width: u32, pub cut_height: u32, pub seam_allowance_width: u32, - pub align_needed: Option, - pub align_marks: Option>, + pub alignment: Option, } pub(crate) fn read_cut_data(input: &[u8]) -> IResult<&[u8], CutData> { @@ -28,28 +26,31 @@ pub(crate) fn read_cut_data(input: &[u8]) -> IResult<&[u8], CutData> { le_u32, le_u32, le_u32, - cond(file_type == FileType::PrintAndCut, bool32), - cond( - file_type == FileType::PrintAndCut, - length_count(le_u32, point::read_point), - ), + cond(file_type == FileType::PrintAndCut, read_alignment_data), )), - move |( - mat_id, - cut_width, - cut_height, - seam_allowance_width, - align_needed, - align_marks, - )| CutData { + move |(mat_id, cut_width, cut_height, seam_allowance_width, alignment)| CutData { file_type, mat_id, cut_width, cut_height, seam_allowance_width, - align_needed, - align_marks, + alignment, }, ) })(input) } + +impl Encode for CutData { + fn encode(&self, buffer: &mut Vec) -> std::io::Result<()> { + self.file_type.encode(buffer)?; + self.mat_id.encode(buffer)?; + self.cut_width.encode(buffer)?; + self.cut_height.encode(buffer)?; + self.seam_allowance_width.encode(buffer)?; + if let Some(alignment) = &self.alignment { + alignment.encode(buffer)?; + } + + Ok(()) + } +} diff --git a/src/encode.rs b/src/encode.rs new file mode 100644 index 0000000..ce3e4f5 --- /dev/null +++ b/src/encode.rs @@ -0,0 +1,96 @@ +use std::io; +use std::io::Write; + +pub(crate) trait Encode { + fn encode(&self, buffer: &mut Vec) -> io::Result<()>; + + fn encode_to_vec(&self) -> io::Result> { + let mut buffer = Vec::new(); + self.encode(&mut buffer)?; + Ok(buffer) + } +} + +impl Encode for u8 { + fn encode(&self, buffer: &mut Vec) -> io::Result<()> { + buffer.write(&self.to_le_bytes())?; + Ok(()) + } +} + +impl Encode for u16 { + fn encode(&self, buffer: &mut Vec) -> io::Result<()> { + buffer.write(&self.to_le_bytes())?; + Ok(()) + } +} + +impl Encode for u32 { + fn encode(&self, buffer: &mut Vec) -> io::Result<()> { + buffer.write(&self.to_le_bytes())?; + Ok(()) + } +} + +impl Encode for u64 { + fn encode(&self, buffer: &mut Vec) -> io::Result<()> { + buffer.write(&self.to_le_bytes())?; + Ok(()) + } +} + +impl Encode for u128 { + fn encode(&self, buffer: &mut Vec) -> io::Result<()> { + buffer.write(&self.to_le_bytes())?; + Ok(()) + } +} + +impl Encode for i8 { + fn encode(&self, buffer: &mut Vec) -> io::Result<()> { + buffer.write(&self.to_le_bytes())?; + Ok(()) + } +} + +impl Encode for i16 { + fn encode(&self, buffer: &mut Vec) -> io::Result<()> { + buffer.write(&self.to_le_bytes())?; + Ok(()) + } +} + +impl Encode for i32 { + fn encode(&self, buffer: &mut Vec) -> io::Result<()> { + buffer.write(&self.to_le_bytes())?; + Ok(()) + } +} + +impl Encode for i64 { + fn encode(&self, buffer: &mut Vec) -> io::Result<()> { + buffer.write(&self.to_le_bytes())?; + Ok(()) + } +} + +impl Encode for i128 { + fn encode(&self, buffer: &mut Vec) -> io::Result<()> { + buffer.write(&self.to_le_bytes())?; + Ok(()) + } +} + +impl Encode for f32 { + fn encode(&self, buffer: &mut Vec) -> io::Result<()> { + buffer.write(&self.to_le_bytes())?; + Ok(()) + } +} + +impl Encode for f64 { + fn encode(&self, buffer: &mut Vec) -> io::Result<()> { + buffer.write(&self.to_le_bytes())?; + Ok(()) + } +} diff --git a/src/error.rs b/src/error.rs index 9e9d104..c3f0a6d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,4 @@ -use std::fmt::{Display, Formatter}; +use std::fmt::{Debug, Display, Formatter}; pub struct Error { pub(crate) message: String, @@ -9,3 +9,9 @@ impl Display for Error { write!(f, "{0}", self.message) } } + +impl Debug for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{0}", self.message) + } +} diff --git a/src/fcm_file.rs b/src/fcm_file.rs index 835b99f..eeb2a10 100644 --- a/src/fcm_file.rs +++ b/src/fcm_file.rs @@ -5,16 +5,17 @@ use nom::sequence::tuple; use nom::IResult; use crate::cut_data::CutData; +use crate::encode::Encode; use crate::error::Error; use crate::file_header::FileHeader; -use crate::piece::Piece; -use crate::{cut_data, file_header, piece}; +use crate::piece_table::PieceTable; +use crate::{cut_data, file_header, piece_table}; #[derive(Debug)] pub struct FcmFile { pub file_header: FileHeader, pub cut_data: CutData, - pub pieces: Vec<(u16, Piece)>, + pub piece_table: PieceTable, } impl FcmFile { @@ -29,10 +30,19 @@ impl FcmFile { let data = fs::read(file.as_ref()).map_err(|e| Error { message: format!("Could not open file: {0}", e), })?; - let (_, file) = read_fcm_file(data.as_slice()).map_err(|e| Error { - message: format!("Could not parse file: {0}", e), - })?; - Ok(file) + FcmFile::from_bytes(data.as_slice()) + } + + pub fn to_bytes(&self) -> Result, Error> { + self.encode_to_vec().map_err(|e| Error { + message: format!("Could not serialize file: {0}", e), + }) + } + + pub fn to_file>(&self, file: T) -> Result<(), Error> { + fs::write(file, self.to_bytes()?.as_slice()).map_err(|e| Error { + message: format!("Could not write to file: {0}", e), + }) } } @@ -41,12 +51,21 @@ pub(crate) fn read_fcm_file(input: &[u8]) -> IResult<&[u8], FcmFile> { tuple(( file_header::read_file_header, cut_data::read_cut_data, - piece::read_piece_table, + piece_table::read_piece_table, )), - |(file_header, cut_data, pieces)| FcmFile { + |(file_header, cut_data, piece_table)| FcmFile { file_header, cut_data, - pieces, + piece_table, }, )(input) } + +impl Encode for FcmFile { + fn encode(&self, buffer: &mut Vec) -> std::io::Result<()> { + self.file_header.encode(buffer)?; + self.cut_data.encode(buffer)?; + self.piece_table.encode(buffer)?; + Ok(()) + } +} diff --git a/src/file_header.rs b/src/file_header.rs index a0b9503..a237718 100644 --- a/src/file_header.rs +++ b/src/file_header.rs @@ -1,17 +1,20 @@ +use std::io::Write; + use nom::combinator::{map, opt}; use nom::multi::{length_data, length_value}; use nom::number::complete::{le_u32, le_u8}; use nom::sequence::tuple; use nom::IResult; -use crate::file_variant::Variant; +use crate::encode::Encode; +use crate::file_variant::FileVariant; use crate::generator::Generator; use crate::util::{bool32, read_length_utf16, read_tag, read_utf8_until_null}; -use crate::{file_variant, generator}; +use crate::{file_variant, generator, util}; #[derive(Debug)] pub struct FileHeader { - pub variant: Variant, + pub variant: FileVariant, pub version: String, pub content_id: u32, pub short_name: String, @@ -65,7 +68,7 @@ pub(crate) fn read_file_header(input: &[u8]) -> IResult<&[u8], FileHeader> { ), )| FileHeader { variant, - version: version, + version, content_id, short_name, long_name, @@ -79,3 +82,35 @@ pub(crate) fn read_file_header(input: &[u8]) -> IResult<&[u8], FileHeader> { }, )(input) } + +impl Encode for FileHeader { + fn encode(&self, buffer: &mut Vec) -> std::io::Result<()> { + self.variant.encode(buffer)?; + buffer.write(&self.version.as_bytes()[0..4])?; + self.content_id.encode(buffer)?; + + let mut variable_header: Vec = vec![]; + + util::write_utf8_fixed(&self.short_name, &mut variable_header)?; + util::write_utf16_str(&self.long_name, &mut variable_header)?; + util::write_utf16_str(&self.author_name, &mut variable_header)?; + util::write_utf16_str(&self.copyright, &mut variable_header)?; + + self.thumbnail_block_size_width + .encode(&mut variable_header)?; + self.thumbnail_block_size_height + .encode(&mut variable_header)?; + (self.thumbnail.len() as u32).encode(&mut variable_header)?; + variable_header.write(&self.thumbnail)?; + + self.generator.encode(&mut variable_header)?; + if let Some(print_to_cut) = &self.print_to_cut { + (*print_to_cut as u32).encode(&mut variable_header)?; + } + + (variable_header.len() as u32).encode(buffer)?; + buffer.write(&variable_header)?; + + Ok(()) + } +} diff --git a/src/file_type.rs b/src/file_type.rs index 9574a15..ba39aac 100644 --- a/src/file_type.rs +++ b/src/file_type.rs @@ -1,7 +1,11 @@ +use std::io::Write; + use nom::combinator::map_res; use nom::number::complete::le_u32; use nom::IResult; +use crate::encode::Encode; + #[derive(Debug, PartialEq, Copy, Clone)] pub enum FileType { Cut, @@ -15,3 +19,13 @@ pub(crate) fn read_file_type(input: &[u8]) -> IResult<&[u8], FileType> { _ => Err(format!("Unable to parse file type: {data}")), })(input) } + +impl Encode for FileType { + fn encode(&self, buffer: &mut Vec) -> std::io::Result<()> { + match self { + FileType::Cut => buffer.write(&0x10u32.to_le_bytes())?, + FileType::PrintAndCut => buffer.write(&0x38u32.to_le_bytes())?, + }; + Ok(()) + } +} diff --git a/src/file_variant.rs b/src/file_variant.rs index 924798f..24bc1fb 100644 --- a/src/file_variant.rs +++ b/src/file_variant.rs @@ -1,17 +1,31 @@ +use std::io::Write; + use nom::branch::alt; use nom::bytes::complete::tag; use nom::combinator::map; use nom::IResult; +use crate::encode::Encode; + #[derive(Debug)] -pub enum Variant { +pub enum FileVariant { FCM, VCM, } -pub(crate) fn read_variant(input: &[u8]) -> IResult<&[u8], Variant> { +pub(crate) fn read_variant(input: &[u8]) -> IResult<&[u8], FileVariant> { alt(( - map(tag("#FCM"), |_| Variant::FCM), - map(tag("#VCM"), |_| Variant::VCM), + map(tag("#FCM"), |_| FileVariant::FCM), + map(tag("#VCM"), |_| FileVariant::VCM), ))(input) } + +impl Encode for FileVariant { + fn encode(&self, buffer: &mut Vec) -> std::io::Result<()> { + match self { + FileVariant::FCM => buffer.write("#FCM".as_bytes())?, + FileVariant::VCM => buffer.write("#VCM".as_bytes())?, + }; + Ok(()) + } +} diff --git a/src/generator.rs b/src/generator.rs index 3543dad..46dc429 100644 --- a/src/generator.rs +++ b/src/generator.rs @@ -1,3 +1,5 @@ +use std::io::Write; + use nom::branch::alt; use nom::bytes::complete::tag; use nom::combinator::map; @@ -5,6 +7,8 @@ use nom::number::complete::le_u32; use nom::sequence::tuple; use nom::IResult; +use crate::encode::Encode; + #[derive(Debug)] pub enum Generator { App(u32), @@ -37,3 +41,23 @@ fn read_generator_device(input: &[u8]) -> IResult<&[u8], Generator> { Generator::Device(device, version) })(input) } + +impl Encode for Generator { + fn encode(&self, buffer: &mut Vec) -> std::io::Result<()> { + match self { + Generator::App(version) => { + buffer.write("1APP".as_bytes())?; + version.encode(buffer)?; + } + Generator::Web(version) => { + buffer.write("1WEB".as_bytes())?; + version.encode(buffer)?; + } + Generator::Device(id, version) => { + id.encode(buffer)?; + version.encode(buffer)?; + } + } + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 92a7f5b..495124b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,5 +19,9 @@ pub mod segment_bezier; pub mod segment_line; mod util; +mod alignment_data; +mod encode; +mod piece_restrictions; +mod piece_table; #[cfg(test)] mod tests; diff --git a/src/outline.rs b/src/outline.rs index be29b7a..b56d457 100644 --- a/src/outline.rs +++ b/src/outline.rs @@ -1,8 +1,11 @@ +use std::io::Write; + use nom::combinator::{flat_map, map}; use nom::multi::length_count; use nom::number::complete::le_u32; use nom::IResult; +use crate::encode::Encode; use crate::outline_tag::OutlineTag; use crate::segment_bezier::SegmentBezier; use crate::segment_line::SegmentLine; @@ -34,3 +37,25 @@ fn read_outline_bezier(input: &[u8]) -> IResult<&[u8], Outline> { |segments| Outline::Bezier(segments), )(input) } + +impl Encode for Outline { + fn encode(&self, buffer: &mut Vec) -> std::io::Result<()> { + match self { + Outline::Line(segments) => { + OutlineTag::Line.encode(buffer)?; + buffer.write(&(segments.len() as u32).to_le_bytes())?; + for segment in segments { + segment.encode(buffer)?; + } + } + Outline::Bezier(segments) => { + OutlineTag::Bezier.encode(buffer)?; + buffer.write(&(segments.len() as u32).to_le_bytes())?; + for segment in segments { + segment.encode(buffer)?; + } + } + } + Ok(()) + } +} diff --git a/src/outline_tag.rs b/src/outline_tag.rs index f0143ac..dccc3ac 100644 --- a/src/outline_tag.rs +++ b/src/outline_tag.rs @@ -1,7 +1,11 @@ +use std::io::Write; + use nom::combinator::map_res; use nom::number::complete::le_u32; use nom::IResult; +use crate::encode::Encode; + #[derive(Debug, Copy, Clone)] pub(crate) enum OutlineTag { Line, @@ -15,3 +19,13 @@ pub(crate) fn read_outline_tag(input: &[u8]) -> IResult<&[u8], OutlineTag> { _ => Err(nom::Err::Failure(format!("Unexpected outline tag: {tag}"))), })(input) } + +impl Encode for OutlineTag { + fn encode(&self, buffer: &mut Vec) -> std::io::Result<()> { + match self { + OutlineTag::Line => buffer.write(&0u32.to_le_bytes())?, + OutlineTag::Bezier => buffer.write(&1u32.to_le_bytes())?, + }; + Ok(()) + } +} diff --git a/src/path.rs b/src/path.rs index 98915b4..186a9b3 100644 --- a/src/path.rs +++ b/src/path.rs @@ -1,9 +1,13 @@ +use std::io; +use std::io::Write; + use nom::combinator::{flat_map, map}; use nom::multi::length_value; use nom::number::complete::le_u32; use nom::sequence::tuple; use nom::IResult; +use crate::encode::Encode; use crate::path_rhinestones::PathRhinestones; use crate::path_shape::PathShape; use crate::path_tool::PathTool; @@ -16,6 +20,48 @@ pub struct Path { pub rhinestones: Option, } +impl Encode for Path { + fn encode(&self, buffer: &mut Vec) -> io::Result<()> { + self.tool.encode(buffer)?; + buffer.write( + &self + .shape + .as_ref() + .map(|it| it.outlines.len() as u32) + .unwrap_or_default() + .to_le_bytes(), + )?; + buffer.write( + &self + .rhinestones + .as_ref() + .map(|it| it.points.len() as u32) + .unwrap_or_default() + .to_le_bytes(), + )?; + buffer.write( + &self + .rhinestones + .as_ref() + .map(|it| it.diameter) + .unwrap_or_default() + .to_le_bytes(), + )?; + if let Some(shape) = &self.shape { + shape.start.encode(buffer)?; + for outline in &shape.outlines { + outline.encode(buffer)?; + } + } + if let Some(rhinestones) = &self.rhinestones { + for rhinestone in &rhinestones.points { + rhinestone.encode(buffer)?; + } + } + Ok(()) + } +} + pub(crate) fn read_path(input: &[u8]) -> IResult<&[u8], Path> { flat_map( tuple(( diff --git a/src/path_tool.rs b/src/path_tool.rs index 06d2443..480a0ee 100644 --- a/src/path_tool.rs +++ b/src/path_tool.rs @@ -1,8 +1,12 @@ +use std::io::Write; + use bitflags::bitflags; use nom::combinator::map_opt; use nom::number::complete::le_u32; use nom::IResult; +use crate::encode::Encode; + bitflags! { #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct PathTool: u32 { @@ -23,3 +27,11 @@ bitflags! { pub fn read_path_tool(input: &[u8]) -> IResult<&[u8], PathTool> { map_opt(le_u32, PathTool::from_bits)(input) } + +impl Encode for PathTool { + fn encode(&self, buffer: &mut Vec) -> std::io::Result<()> { + buffer.write(&4u32.to_le_bytes())?; + buffer.write(&self.bits().to_le_bytes())?; + Ok(()) + } +} diff --git a/src/piece.rs b/src/piece.rs index 53a9346..bc4ab08 100644 --- a/src/piece.rs +++ b/src/piece.rs @@ -1,30 +1,17 @@ -use bitflags::bitflags; +use std::io::Write; + use nom::bytes::complete::take; -use nom::combinator::{cond, flat_map, map, map_opt, map_res}; +use nom::combinator::{cond, flat_map, map, map_res}; use nom::multi::{length_count, length_data, length_value}; -use nom::number::complete::{le_f32, le_u16, le_u32}; +use nom::number::complete::{le_f32, le_u32}; use nom::sequence::tuple; use nom::IResult; -use crate::path; +use crate::encode::Encode; use crate::path::Path; -use crate::util::{bool32, read_from_offsets}; - -pub fn read_piece_table(input: &[u8]) -> IResult<&[u8], Vec<(u16, Piece)>> { - flat_map( - tuple(( - length_count(le_u32, le_u32), - le_u32, - length_count(le_u32, le_u16), - )), - move |(offsets, total_length, ids)| { - map( - read_from_offsets(offsets, total_length, read_piece), - move |pieces| ids.clone().into_iter().zip(pieces).collect(), - ) - }, - )(input) -} +use crate::piece_restrictions::PieceRestrictions; +use crate::util::bool32; +use crate::{path, piece_restrictions}; #[derive(Debug)] pub struct Piece { @@ -52,7 +39,7 @@ pub(crate) fn read_piece(input: &[u8]) -> IResult<&[u8], Piece> { }), le_u32, le_u32, - read_piece_restrictions, + piece_restrictions::read_piece_restrictions, read_piece_label, length_count(le_u32, length_value(le_u32, path::read_path)), )), @@ -79,25 +66,6 @@ pub(crate) fn read_piece(input: &[u8]) -> IResult<&[u8], Piece> { )(input) } -bitflags! { - #[derive(Debug, Copy, Clone, PartialEq, Eq)] - pub struct PieceRestrictions: u32 { - const LICENSE_DESIGN = 0x0001; - const SEAM_ALLOWANCE = 0x0002; - const PROHIBITION_OF_SEAM_ALLOWANCE_SETTING = 0x0004; - const NO_ASPECT_RATIO_CHANGE_PROHIBITED = 0x0020; - const JUDGE_BY_USING_PERFECT_MASK_AT_AUTO_LAYOUT = 0x0020; - const TEST_PATTERN = 0x0040; - const PROHIBITION_OF_EDIT = 0x0080; - const PROHIBITION_OF_TOOL = 0x0100; - const _ = !0; - } -} - -fn read_piece_restrictions(input: &[u8]) -> IResult<&[u8], PieceRestrictions> { - map_opt(le_u32, PieceRestrictions::from_bits)(input) -} - fn read_piece_label(input: &[u8]) -> IResult<&[u8], String> { map_res(length_data(le_u32), |label_data: &[u8]| { if label_data[0] == 1 { @@ -107,3 +75,47 @@ fn read_piece_label(input: &[u8]) -> IResult<&[u8], String> { } })(input) } + +impl Encode for Piece { + fn encode(&self, buffer: &mut Vec) -> std::io::Result<()> { + 0u32.encode(buffer)?; + 0u32.encode(buffer)?; + self.width.encode(buffer)?; + self.height.encode(buffer)?; + if let Some(transform) = self.transform { + 1u32.encode(buffer)?; + transform.0.encode(buffer)?; + transform.1.encode(buffer)?; + transform.2.encode(buffer)?; + transform.3.encode(buffer)?; + transform.4.encode(buffer)?; + transform.5.encode(buffer)?; + } else { + 0u32.encode(buffer)?; + } + self.expansion_limit_value.encode(buffer)?; + self.reduction_limit_value.encode(buffer)?; + self.restriction_flags.encode(buffer)?; + + 4u32.encode(buffer)?; + if self.label.is_empty() { + 0u32.encode(buffer)?; + } else { + 1u8.encode(buffer)?; + buffer.write(&self.label.as_bytes()[0..3])?; + } + + let mut path_data: Vec> = vec![]; + for path in &self.paths { + path_data.push(path.encode_to_vec()?); + } + + (path_data.len() as u32).encode(buffer)?; + for path in path_data { + (path.len() as u32).encode(buffer)?; + buffer.write(&path)?; + } + + Ok(()) + } +} diff --git a/src/piece_restrictions.rs b/src/piece_restrictions.rs new file mode 100644 index 0000000..4adc7f8 --- /dev/null +++ b/src/piece_restrictions.rs @@ -0,0 +1,33 @@ +use std::io::Write; + +use bitflags::bitflags; +use nom::combinator::map_opt; +use nom::number::complete::le_u32; +use nom::IResult; + +use crate::encode::Encode; + +bitflags! { + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + pub struct PieceRestrictions: u32 { + const LICENSE_DESIGN = 0x0001; + const SEAM_ALLOWANCE = 0x0002; + const PROHIBITION_OF_SEAM_ALLOWANCE_SETTING = 0x0004; + const NO_ASPECT_RATIO_CHANGE_PROHIBITED = 0x0020; + const JUDGE_BY_USING_PERFECT_MASK_AT_AUTO_LAYOUT = 0x0020; + const TEST_PATTERN = 0x0040; + const PROHIBITION_OF_EDIT = 0x0080; + const PROHIBITION_OF_TOOL = 0x0100; + const _ = !0; + } +} +pub(crate) fn read_piece_restrictions(input: &[u8]) -> IResult<&[u8], PieceRestrictions> { + map_opt(le_u32, PieceRestrictions::from_bits)(input) +} + +impl Encode for PieceRestrictions { + fn encode(&self, buffer: &mut Vec) -> std::io::Result<()> { + buffer.write(&self.bits().to_le_bytes())?; + Ok(()) + } +} diff --git a/src/piece_table.rs b/src/piece_table.rs new file mode 100644 index 0000000..9be05c7 --- /dev/null +++ b/src/piece_table.rs @@ -0,0 +1,61 @@ +use std::io::Write; + +use nom::combinator::{flat_map, map}; +use nom::multi::length_count; +use nom::number::complete::{le_u16, le_u32}; +use nom::sequence::tuple; +use nom::IResult; + +use crate::encode::Encode; +use crate::piece; +use crate::piece::Piece; +use crate::util::read_from_offsets; + +#[derive(Debug)] +pub struct PieceTable { + pieces: Vec<(u16, Piece)>, +} + +pub fn read_piece_table(input: &[u8]) -> IResult<&[u8], PieceTable> { + flat_map( + tuple(( + length_count(le_u32, le_u32), + le_u32, + length_count(le_u32, le_u16), + )), + move |(offsets, total_length, ids)| { + map( + read_from_offsets(offsets, total_length, piece::read_piece), + move |pieces| PieceTable { + pieces: ids.clone().into_iter().zip(pieces).collect(), + }, + ) + }, + )(input) +} + +impl Encode for PieceTable { + fn encode(&self, buffer: &mut Vec) -> std::io::Result<()> { + let mut piece_data: Vec<(u16, Vec)> = vec![]; + for (id, piece) in &self.pieces { + piece_data.push((*id, piece.encode_to_vec()?)); + } + + (piece_data.len() as u32).encode(buffer)?; + let mut offset: u32 = 0; + for (_, data) in &piece_data { + offset.encode(buffer)?; + offset += data.len() as u32; + } + offset.encode(buffer)?; + (piece_data.len() as u32).encode(buffer)?; + for (id, _) in &piece_data { + id.encode(buffer)?; + } + for (_, data) in &piece_data { + buffer.write(data)?; + } + + Ok(()) + } +} diff --git a/src/point.rs b/src/point.rs index 9850480..3734a7b 100644 --- a/src/point.rs +++ b/src/point.rs @@ -1,7 +1,9 @@ +use crate::encode::Encode; use nom::combinator::map; use nom::number::complete::le_i32; use nom::sequence::tuple; use nom::IResult; +use std::io::Write; #[derive(Default, Debug, Copy, Clone)] pub struct Point { @@ -12,3 +14,11 @@ pub struct Point { pub(crate) fn read_point(input: &[u8]) -> IResult<&[u8], Point> { map(tuple((le_i32, le_i32)), |(x, y)| Point { x, y })(input) } + +impl Encode for Point { + fn encode(&self, buffer: &mut Vec) -> std::io::Result<()> { + buffer.write(&self.x.to_le_bytes())?; + buffer.write(&self.y.to_le_bytes())?; + Ok(()) + } +} diff --git a/src/segment_bezier.rs b/src/segment_bezier.rs index 915f433..25ae300 100644 --- a/src/segment_bezier.rs +++ b/src/segment_bezier.rs @@ -1,3 +1,4 @@ +use crate::encode::Encode; use crate::point::{read_point, Point}; use nom::combinator::map; use nom::sequence::tuple; @@ -20,3 +21,12 @@ pub(crate) fn read_segment_bezier(input: &[u8]) -> IResult<&[u8], SegmentBezier> }, )(input) } + +impl Encode for SegmentBezier { + fn encode(&self, buffer: &mut Vec) -> std::io::Result<()> { + self.control1.encode(buffer)?; + self.control2.encode(buffer)?; + self.end.encode(buffer)?; + Ok(()) + } +} diff --git a/src/segment_line.rs b/src/segment_line.rs index 8014e84..fb483e6 100644 --- a/src/segment_line.rs +++ b/src/segment_line.rs @@ -1,3 +1,4 @@ +use crate::encode::Encode; use crate::point::{read_point, Point}; use nom::combinator::map; use nom::IResult; @@ -10,3 +11,10 @@ pub struct SegmentLine { pub(crate) fn read_segment_line(input: &[u8]) -> IResult<&[u8], SegmentLine> { map(read_point, |end| SegmentLine { end })(input) } + +impl Encode for SegmentLine { + fn encode(&self, buffer: &mut Vec) -> std::io::Result<()> { + self.end.encode(buffer)?; + Ok(()) + } +} diff --git a/src/tests.rs b/src/tests.rs index 258384d..4269e9b 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,12 +1,7 @@ -use std::fs::read; - -use crate::fcm_file::{FcmFile, read_fcm_file}; +use crate::fcm_file::FcmFile; fn read_file(path: &str) -> FcmFile { - let file = read(path).expect(format!("could not open file: {path}").as_str()); - let (_, file) = - read_fcm_file(file.as_slice()).expect(format!("could not parse file: {path}").as_str()); - file + FcmFile::from_file(path).unwrap() } #[test] @@ -5643,3 +5638,11 @@ fn parses_test_text() { fn parses_test_thicc() { read_file("samples/test/thicc.fcm"); } + +#[test] +fn roundtrips_test_square() { + let original = std::fs::read("samples/test/square.fcm").unwrap(); + let parsed = FcmFile::from_bytes(original.as_slice()).unwrap(); + let serialized = parsed.to_bytes().unwrap(); + std::fs::write("samples/test/square_roundtrip.fcm", serialized).unwrap(); +} diff --git a/src/util.rs b/src/util.rs index 0970610..25e362f 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,9 +1,11 @@ +use crate::encode::Encode; use nom::bytes::complete::{take, take_while}; use nom::combinator::{map, map_parser, map_res}; use nom::error::{ErrorKind, ParseError}; use nom::multi::length_count; use nom::number::complete::{le_u16, le_u32, le_u8}; use nom::{IResult, Parser}; +use std::io::Write; pub(crate) fn bool32(input: &[u8]) -> IResult<&[u8], bool> { map(le_u32, |it| it != 0)(input) @@ -62,3 +64,21 @@ where Ok((&input[total_length as usize..], pieces)) } } + +pub fn write_utf16_str(data: &str, buffer: &mut Vec) -> std::io::Result<()> { + let mut data = String::from(data); + data.truncate(255); + (data.len() as u8).encode(buffer)?; + for char in data.encode_utf16() { + char.encode(buffer)?; + } + Ok(()) +} + +pub fn write_utf8_fixed(data: &str, buffer: &mut Vec) -> std::io::Result<()> { + let mut result: Vec = vec![]; + result.write(data.as_bytes())?; + result.write(&[0, 0, 0, 0, 0, 0, 0, 0])?; + buffer.write(&result[0..8])?; + Ok(()) +}