Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(hwp): CommonProperties 남은 속성 파싱 #74

Merged
merged 1 commit into from
Nov 5, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
273 changes: 254 additions & 19 deletions crates/hwp/src/hwp/paragraph/control/common_properties.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,59 @@ use super::paragraph_list::ParagraphList;
pub struct CommonProperties {
/// 컨트롤 ID
pub ctrl_id: u32,
// 오프셋
/// 글자처럼 취급 여부
pub treat_as_char: bool,
/// 줄 간격에 영향을 줄지 여부
/// `treat_as_char` 속성이 true일 때에만 사용한다
pub affect_letter_spacing: bool,
/// 세로 위치의 기준
/// `treat_as_char` 속성이 true일 때에만 사용한다
pub vertical_relative_to: VerticalRelativeTo,
/// 세로 위치의 기준에 대한 상대적인 배열 방식
/// `treat_as_char` 속성이 true일 때에만 사용한다
pub vertical_align: Option<Align>,
/// 가로 위치의 기준
/// `treat_as_char` 속성이 true일 때에만 사용한다
pub horizontal_relative_to: HorizontalRelativeTo,
/// 가로 위치의 기준에 대한 상대적인 배열 방식
/// `treat_as_char` 속성이 true일 때에만 사용한다
pub horizontal_align: Option<Align>,
/// 오브젝트의 세로 위치를 본문 영역으로 제한할지 여부
/// `vertical_relative_to` 속성이 Paragraph 일때만 사용한다
pub flow_with_text: Option<bool>,
/// 다른 오브젝트와 겹치는 것을 허용할지 여부
/// `treat_as_char` 속성이 false일 때에만 사용한다
/// `flow_with_text` 속성이 true면 무조건 false로 간주함
pub allow_overlap: Option<bool>,
/// 오브젝트 폭의 기준
pub width_relative_to: WidthRelativeTo,
/// 오브젝트 높이의 기준
pub height_relative_to: HeightRelativeTo,
/// 크기 보호 여부
/// `vertical_relative_to` 속성이 Paragraph 일때만 사용한다
pub protect: Option<bool>,
/// 오브젝트 주위를 텍스트가 어떻게 흘러갈지 지정하는 옵션
/// `treat_as_char` 속성이 false일 때에만 사용한다
pub text_wrap: Option<TextWrap>,
/// 오브젝트의 좌/우 어느 쪽에 글을 배치할지 지정하는 옵션
/// `text_wrap` 속성이 Square, Tight, Through 일때 사용한다
pub text_flow: Option<TextFlow>,
/// 이 개체가 속하는 번호 범주
pub numbering_kind: NumberingKind,
/// 오프셋
pub offset: Offset,
/// width 오브젝트의 폭
/// 오브젝트의 폭
pub width: u32,
/// height 오브젝트의 높이
/// 오브젝트의 높이
pub height: u32,
/// z-index
/// z-order
pub z_order: i32,
/// 오브젝트의 바깥 4방향 여백
pub margin: [i16; 4],
/// 문서 내 각 개체에 대한 고유 아이디
pub instance_id: u32,
/// 쪽 나눔 방지
pub prevent_page_break: bool,
/// 개체 설명문
pub description: String,
/// 캡션
Expand All @@ -40,10 +83,66 @@ impl CommonProperties {

let ctrl_id = reader.read_u32::<LittleEndian>().unwrap();

// 속성
reader.read_u32::<LittleEndian>().unwrap();
let attribute = reader.read_u32::<LittleEndian>().unwrap();
let treat_as_char = get_flag(attribute, 0);
let affect_letter_spacing = get_flag(attribute, 2);
let vertical_relative_to =
VerticalRelativeTo::from_u32(get_value_range(attribute, 3, 4)).unwrap();
let vertical_align = if treat_as_char {
Some(map_to_align(
get_value_range(attribute, 5, 7),
vertical_relative_to.clone() as u8,
))
} else {
None
};
let horizontal_relative_to =
HorizontalRelativeTo::from_u32(get_value_range(attribute, 8, 9)).unwrap();
let horizontal_align = if treat_as_char {
Some(map_to_align(
get_value_range(attribute, 10, 12),
horizontal_relative_to.clone() as u8,
))
} else {
None
};
let flow_with_text = if vertical_relative_to == VerticalRelativeTo::Paragraph {
Some(get_flag(attribute, 13))
} else {
None
};
let allow_overlap = if treat_as_char {
None
} else if flow_with_text.is_some() && flow_with_text.unwrap() == true {
Some(false)
} else {
Some(get_flag(attribute, 14))
};
let width_relative_to =
WidthRelativeTo::from_u32(get_value_range(attribute, 15, 17)).unwrap();
let height_relative_to =
HeightRelativeTo::from_u32(get_value_range(attribute, 18, 19)).unwrap();
let protect = if vertical_relative_to == VerticalRelativeTo::Paragraph {
Some(get_flag(attribute, 20))
} else {
None
};
let text_wrap = if treat_as_char {
None
} else {
Some(TextWrap::from_u32(get_value_range(attribute, 21, 23)).unwrap())
};
let text_flow = match text_wrap {
Some(TextWrap::Square) | Some(TextWrap::Tight) | Some(TextWrap::Through) => {
Some(TextFlow::from_u32(get_value_range(attribute, 24, 25)).unwrap())
}
_ => None,
};

// NOTE: (@hahnlee) 배포용 문서에서 넘어가는 경우가 있음. 확인한 문서에서는 mod 값과 동일함
let numbering_kind =
NumberingKind::from_u32(get_value_range(attribute, 26, 28) % 4).unwrap();

// 세로 오프셋 값
let offset = Offset {
vertical: reader.read_u32::<LittleEndian>().unwrap(),
horizontal: reader.read_u32::<LittleEndian>().unwrap(),
Expand All @@ -53,16 +152,16 @@ impl CommonProperties {
let height = reader.read_u32::<LittleEndian>().unwrap();
let z_order = reader.read_i32::<LittleEndian>().unwrap();

// 2x4 오브젝트의 바깥 4방향 여백 방향확인 필요
reader.read_i16::<LittleEndian>().unwrap();
reader.read_i16::<LittleEndian>().unwrap();
reader.read_i16::<LittleEndian>().unwrap();
reader.read_i16::<LittleEndian>().unwrap();
let margin = [
reader.read_i16::<LittleEndian>().unwrap(),
reader.read_i16::<LittleEndian>().unwrap(),
reader.read_i16::<LittleEndian>().unwrap(),
reader.read_i16::<LittleEndian>().unwrap(),
];

let instance_id = reader.read_u32::<LittleEndian>().unwrap();

// 쪽나눔 방지 on(1) / off(0)
reader.read_i32::<LittleEndian>().unwrap();
let prevent_page_break = reader.read_i32::<LittleEndian>().unwrap() == 0;

// NOTE: (@hahnlee) len이 0이 아니라 아예 값이 없을 수도 있다
let description = if reader.stream_position().unwrap() < size {
Expand All @@ -85,17 +184,142 @@ impl CommonProperties {

Self {
ctrl_id,
treat_as_char,
affect_letter_spacing,
vertical_relative_to,
vertical_align,
horizontal_relative_to,
horizontal_align,
flow_with_text,
allow_overlap,
width_relative_to,
height_relative_to,
protect,
text_wrap,
text_flow,
numbering_kind,
offset,
width,
height,
z_order,
margin,
instance_id,
prevent_page_break,
description,
caption,
}
}
}

/// 세로 위치의 기준
#[repr(u8)]
#[derive(Debug, Clone, PartialEq, FromPrimitive)]
pub enum VerticalRelativeTo {
Paper,
Page,
Paragraph,
}

/// 배열 방식
#[repr(u8)]
#[derive(Debug, Clone, PartialEq)]
pub enum Align {
Top,
Center,
Bottom,
Left,
Right,
Inside,
Outside,
}

fn map_to_align(value: u32, rel_to: u8) -> Align {
if rel_to == 0 || rel_to == 1 {
match value {
0 => Align::Top,
1 => Align::Center,
2 => Align::Bottom,
3 => Align::Inside,
4 => Align::Outside,
_ => panic!("잘못된 값입니다."),
}
} else {
match value {
0 => Align::Left,
2 => Align::Right,
_ => panic!("잘못된 값입니다."),
}
}
}

/// 가로 배열 방식
#[repr(u8)]
#[derive(Debug, Clone, PartialEq, FromPrimitive)]
pub enum HorizontalRelativeTo {
Paper,
Page,
Column,
Paragraph,
}

/// 오브젝트 폭의 기준
#[repr(u8)]
#[derive(Debug, Clone, PartialEq, FromPrimitive)]
pub enum WidthRelativeTo {
Paper,
Page,
Column,
Paragraph,
Absolute,
}

/// 오브젝트 높이의 기준
#[repr(u8)]
#[derive(Debug, Clone, PartialEq, FromPrimitive)]
pub enum HeightRelativeTo {
Paper,
Page,
Absolute,
}

/// 오브젝트 주위를 텍스트가 어떻게 흘러갈지 지정하는 옵션
#[repr(u8)]
#[derive(Debug, Clone, PartialEq, FromPrimitive)]
pub enum TextWrap {
/// bound rect를 따라
Square,
/// 오브젝트의 outline을 따라
Tight,
/// 오브젝트 내부의 빈 공간까지
Through,
/// 좌, 우에는 텍스트를 배치하지 않음
TopAndBottom,
/// 글과 겹치게 하여 글 뒤로
BehindText,
/// 글과 겹치게 하여 글 앞으로
InFrontOfText,
}

/// 오브젝트의 좌/우 어느 쪽에 글을 배치할지
#[repr(u8)]
#[derive(Debug, Clone, PartialEq, FromPrimitive)]
pub enum TextFlow {
BothSides,
LeftOnly,
RightOnly,
LargestOnly,
}

/// 이 개체가 속하는 번호 범주
#[repr(u8)]
#[derive(Debug, Clone, PartialEq, FromPrimitive)]
pub enum NumberingKind {
None,
Figure,
Table,
Equation,
}

#[derive(Debug, Clone)]
pub struct Offset {
pub vertical: u32,
Expand All @@ -109,7 +333,13 @@ pub struct Caption {
/// 방향
pub align: CaptionAlign,
/// 캡션 폭에 마진을 포함할 지 여부 (가로 방향일 때만 사용)
pub include_margin: bool,
pub full_size: bool,
/// 캡션 폭(세로 방향일 때만 사용)
pub width: u32,
/// 캡션과 틀 사이 간격
pub gap: i16,
/// 텍스트의 최대 길이(=개체의 폭)
pub last_width: u32,
}

impl Caption {
Expand All @@ -128,14 +358,19 @@ impl Caption {

let attribute = reader.read_u32::<LittleEndian>().unwrap();
let align = CaptionAlign::from_u32(get_value_range(attribute, 0, 1)).unwrap();
let include_margin = get_flag(attribute, 2);
let full_size = get_flag(attribute, 2);

let width = reader.read_u32::<LittleEndian>().unwrap();
let gap = reader.read_i16::<LittleEndian>().unwrap();
let last_width = reader.read_u32::<LittleEndian>().unwrap();

// TODO: (@hahnlee) 남은데이터 파싱하기
// NOTE: (@hahnlee) 바이트가 문서와 다름...
Self {
paragraph_list,
align,
include_margin,
full_size,
width,
gap,
last_width,
}
}
}
Expand Down