Improve spacing (#367)

* fix: line spacing interfaces

* fix: js interface

* fix: js if

* fix: reader and testing
main
bokuweb 2021-11-25 19:42:06 +09:00 committed by GitHub
parent f1044c887c
commit c6a555aa75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 3212 additions and 277 deletions

View File

@ -4,7 +4,7 @@ use std::fs::File;
use std::io::{Read, Write}; use std::io::{Read, Write};
pub fn main() { pub fn main() {
let mut file = File::open("./fixtures/custom/custom.docx").unwrap(); let mut file = File::open("./spacing.docx").unwrap();
let mut buf = vec![]; let mut buf = vec![];
file.read_to_end(&mut buf).unwrap(); file.read_to_end(&mut buf).unwrap();

View File

@ -4,7 +4,7 @@ use crate::xml_builder::*;
use crate::line_spacing_type::LineSpacingType; use crate::line_spacing_type::LineSpacingType;
use serde::*; use serde::*;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct LineSpacing { pub struct LineSpacing {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
@ -14,28 +14,45 @@ pub struct LineSpacing {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
after: Option<u32>, after: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
before_lines: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
after_lines: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
line: Option<u32>, line: Option<u32>,
} }
impl LineSpacing { impl LineSpacing {
pub fn new(spacing: Option<LineSpacingType>) -> Self { pub fn new() -> Self {
Self { Self::default()
line_rule: spacing,
before: None,
after: None,
line: None,
} }
}
pub fn before(mut self, before: Option<u32>) -> Self { pub fn line_rule(mut self, t: LineSpacingType) -> Self {
self.before = before; self.line_rule = Some(t);
self self
} }
pub fn after(mut self, after: Option<u32>) -> Self {
self.after = after; pub fn before(mut self, before: u32) -> Self {
self.before = Some(before);
self self
} }
pub fn line(mut self, line: Option<u32>) -> Self {
self.line = line; pub fn after(mut self, after: u32) -> Self {
self.after = Some(after);
self
}
pub fn before_lines(mut self, before: u32) -> Self {
self.before_lines = Some(before);
self
}
pub fn after_lines(mut self, after: u32) -> Self {
self.after_lines = Some(after);
self
}
pub fn line(mut self, line: u32) -> Self {
self.line = Some(line);
self self
} }
} }
@ -43,7 +60,14 @@ impl LineSpacing {
impl BuildXML for LineSpacing { impl BuildXML for LineSpacing {
fn build(&self) -> Vec<u8> { fn build(&self) -> Vec<u8> {
let b = XMLBuilder::new(); let b = XMLBuilder::new();
b.line_spacing(self.before, self.after, self.line, self.line_rule) b.line_spacing(
self.before,
self.after,
self.line,
self.before_lines,
self.after_lines,
self.line_rule,
)
.build() .build()
} }
} }
@ -58,8 +82,9 @@ mod tests {
#[test] #[test]
fn test_spacing() { fn test_spacing() {
let b = LineSpacing::new(Some(LineSpacingType::Auto)) let b = LineSpacing::new()
.line(Some(100)) .line_rule(LineSpacingType::Auto)
.line(100)
.build(); .build();
assert_eq!( assert_eq!(
str::from_utf8(&b).unwrap(), str::from_utf8(&b).unwrap(),
@ -67,12 +92,26 @@ mod tests {
); );
} }
#[test]
fn test_spacing_after_lines() {
let b = LineSpacing::new()
.line_rule(LineSpacingType::Auto)
.after_lines(100)
.build();
assert_eq!(
str::from_utf8(&b).unwrap(),
r#"<w:spacing w:afterLines="100" w:lineRule="auto" />"#
);
}
#[test] #[test]
fn test_spacing_json() { fn test_spacing_json() {
let s = LineSpacing { let s = LineSpacing {
line_rule: Some(LineSpacingType::Auto), line_rule: Some(LineSpacingType::Auto),
before: None, before: None,
after: None, after: None,
before_lines: None,
after_lines: None,
line: Some(100), line: Some(100),
}; };
assert_eq!( assert_eq!(

View File

@ -244,16 +244,8 @@ impl Paragraph {
self self
} }
pub fn line_spacing( pub fn line_spacing(mut self, spacing: LineSpacing) -> Self {
mut self, self.property = self.property.line_spacing(spacing);
before: Option<u32>,
after: Option<u32>,
line: Option<u32>,
spacing_type: Option<LineSpacingType>,
) -> Self {
self.property = self
.property
.line_spacing(before, after, line, spacing_type);
self self
} }
} }
@ -334,8 +326,13 @@ mod tests {
#[test] #[test]
fn test_line_spacing_and_character_spacing() { fn test_line_spacing_and_character_spacing() {
let spacing = LineSpacing::new()
.line_rule(LineSpacingType::Auto)
.before(20)
.after(30)
.line(200);
let b = Paragraph::new() let b = Paragraph::new()
.line_spacing(Some(20), Some(30), Some(200), Some(LineSpacingType::Auto)) .line_spacing(spacing)
.add_run(Run::new().add_text("Hello")) .add_run(Run::new().add_text("Hello"))
.build(); .build();
assert_eq!( assert_eq!(

View File

@ -2,7 +2,7 @@ use serde::Serialize;
use super::*; use super::*;
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::types::{AlignmentType, LineSpacingType, SpecialIndentType}; use crate::types::{AlignmentType, SpecialIndentType};
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Serialize, Debug, Clone, PartialEq)] #[derive(Serialize, Debug, Clone, PartialEq)]
@ -78,19 +78,8 @@ impl ParagraphProperty {
self self
} }
pub fn line_spacing( pub fn line_spacing(mut self, spacing: LineSpacing) -> Self {
mut self, self.line_spacing = Some(spacing);
before: Option<u32>,
after: Option<u32>,
line: Option<u32>,
spacing_type: Option<LineSpacingType>,
) -> Self {
self.line_spacing = Some(
LineSpacing::new(spacing_type)
.after(after)
.before(before)
.line(line),
);
self self
} }
@ -173,6 +162,7 @@ impl BuildXML for ParagraphProperty {
mod tests { mod tests {
use super::*; use super::*;
use crate::types::LineSpacingType;
#[cfg(test)] #[cfg(test)]
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use std::str; use std::str;
@ -238,9 +228,10 @@ mod tests {
#[test] #[test]
fn test_line_spacing() { fn test_line_spacing() {
let props = ParagraphProperty::new(); let props = ParagraphProperty::new();
let bytes = props let spacing = LineSpacing::new()
.line_spacing(None, None, Some(100), Some(LineSpacingType::AtLeast)) .line_rule(LineSpacingType::AtLeast)
.build(); .line(100);
let bytes = props.line_spacing(spacing).build();
assert_eq!( assert_eq!(
str::from_utf8(&bytes).unwrap(), str::from_utf8(&bytes).unwrap(),
r#"<w:pPr><w:rPr /><w:spacing w:line="100" w:lineRule="atLeast" /></w:pPr>"# r#"<w:pPr><w:rPr /><w:spacing w:line="100" w:lineRule="atLeast" /></w:pPr>"#

View File

@ -1,34 +1,34 @@
use crate::line_spacing_type::LineSpacingType; use crate::line_spacing_type::LineSpacingType;
use crate::LineSpacing;
use crate::ReaderError; use crate::ReaderError;
use std::str::FromStr; use std::str::FromStr;
use xml::attribute::OwnedAttribute; use xml::attribute::OwnedAttribute;
pub type LineSpacingResult = Result< pub fn read_line_spacing(attributes: &[OwnedAttribute]) -> Result<LineSpacing, ReaderError> {
( let mut spacing = LineSpacing::new();
Option<u32>,
Option<u32>,
Option<u32>,
Option<LineSpacingType>,
),
ReaderError,
>;
pub fn read_line_spacing(attributes: &[OwnedAttribute]) -> LineSpacingResult {
let mut before: Option<u32> = None;
let mut after: Option<u32> = None;
let mut line: Option<u32> = None;
let mut spacing_type: Option<LineSpacingType> = None;
for a in attributes { for a in attributes {
let local_name = &a.name.local_name; let local_name = &a.name.local_name;
if local_name == "before" { match local_name.as_str() {
before = Some(u32::from_str(&a.value)?); "before" => {
} else if local_name == "after" { spacing = spacing.before(u32::from_str(&a.value)?);
after = Some(u32::from_str(&a.value)?); }
} else if local_name == "line" { "after" => {
line = Some(u32::from_str(&a.value)?); spacing = spacing.after(u32::from_str(&a.value)?);
} else if local_name == "lineRule" { }
spacing_type = Some(LineSpacingType::from_str(&a.value)?); "line" => {
spacing = spacing.line(u32::from_str(&a.value)?);
}
"lineRule" => {
spacing = spacing.line_rule(LineSpacingType::from_str(&a.value)?);
}
"beforeLines" => {
spacing = spacing.before_lines(u32::from_str(&a.value)?);
}
"afterLines" => {
spacing = spacing.after_lines(u32::from_str(&a.value)?);
}
_ => {}
} }
} }
Ok((before, after, line, spacing_type)) Ok(spacing)
} }

View File

@ -40,9 +40,11 @@ impl ElementReader for ParagraphProperty {
continue; continue;
} }
XMLElement::Spacing => { XMLElement::Spacing => {
let (before, after, line, spacing_type) = if let Ok(spacing) =
attributes::line_spacing::read_line_spacing(&attributes)?; attributes::line_spacing::read_line_spacing(&attributes)
p = p.line_spacing(before, after, line, spacing_type); {
p = p.line_spacing(spacing);
}
continue; continue;
} }
XMLElement::Justification => { XMLElement::Justification => {

View File

@ -167,11 +167,15 @@ impl XMLBuilder {
before: Option<u32>, before: Option<u32>,
after: Option<u32>, after: Option<u32>,
line: Option<u32>, line: Option<u32>,
before_lines: Option<u32>,
after_lines: Option<u32>,
spacing: Option<LineSpacingType>, spacing: Option<LineSpacingType>,
) -> Self { ) -> Self {
let mut xml_event = XmlEvent::start_element("w:spacing"); let mut xml_event = XmlEvent::start_element("w:spacing");
let before_val: String; let before_val: String;
let after_val: String; let after_val: String;
let before_lines_val: String;
let after_lines_val: String;
let line_val: String; let line_val: String;
if let Some(before) = before { if let Some(before) = before {
@ -182,6 +186,14 @@ impl XMLBuilder {
after_val = format!("{}", after); after_val = format!("{}", after);
xml_event = xml_event.attr("w:after", &after_val) xml_event = xml_event.attr("w:after", &after_val)
} }
if let Some(before_lines) = before_lines {
before_lines_val = format!("{}", before_lines);
xml_event = xml_event.attr("w:beforeLines", &before_lines_val)
}
if let Some(after_lines) = after_lines {
after_lines_val = format!("{}", after_lines);
xml_event = xml_event.attr("w:afterLines", &after_lines_val)
}
if let Some(line) = line { if let Some(line) = line {
line_val = format!("{}", line); line_val = format!("{}", line);
xml_event = xml_event.attr("w:line", &line_val) xml_event = xml_event.attr("w:line", &line_val)

View File

@ -420,21 +420,36 @@ pub fn date() -> Result<(), DocxError> {
pub fn line_spacing() -> Result<(), DocxError> { pub fn line_spacing() -> Result<(), DocxError> {
let path = std::path::Path::new("./tests/output/line_spacing.docx"); let path = std::path::Path::new("./tests/output/line_spacing.docx");
let file = std::fs::File::create(&path).unwrap(); let file = std::fs::File::create(&path).unwrap();
Docx::new() Docx::new()
.add_paragraph( .add_paragraph(
Paragraph::new() Paragraph::new()
.add_run(Run::new().add_text(DUMMY)) .add_run(Run::new().add_text(DUMMY))
.line_spacing(Some(300), None, Some(300), Some(LineSpacingType::Auto)), .line_spacing(
LineSpacing::new()
.before(300)
.line(300)
.line_rule(LineSpacingType::Auto),
),
) )
.add_paragraph( .add_paragraph(
Paragraph::new() Paragraph::new()
.add_run(Run::new().add_text(DUMMY)) .add_run(Run::new().add_text(DUMMY))
.line_spacing(None, None, Some(300), Some(LineSpacingType::AtLeast)), .line_spacing(
LineSpacing::new()
.line(300)
.line_rule(LineSpacingType::AtLeast),
),
) )
.add_paragraph( .add_paragraph(
Paragraph::new() Paragraph::new()
.add_run(Run::new().add_text(DUMMY).character_spacing(100)) .add_run(Run::new().add_text(DUMMY).character_spacing(100))
.line_spacing(None, Some(300), Some(300), Some(LineSpacingType::Exact)), .line_spacing(
LineSpacing::new()
.after(300)
.line(300)
.line_rule(LineSpacingType::Exact),
),
) )
.build() .build()
.pack(file)?; .pack(file)?;

View File

@ -1,4 +1,4 @@
import { Paragraph } from "./paragraph"; import { Paragraph, ParagraphProperty } from "./paragraph";
import { Insert } from "./insert"; import { Insert } from "./insert";
import { Delete } from "./delete"; import { Delete } from "./delete";
import { DeleteText } from "./delete-text"; import { DeleteText } from "./delete-text";
@ -339,6 +339,51 @@ export class Docx {
return comment; return comment;
} }
buildLineSpacing(p: ParagraphProperty): wasm.LineSpacing | null {
const { lineSpacing } = p;
if (lineSpacing == null) return null;
let kind;
switch (lineSpacing._lineRule) {
case "atLeast": {
kind = wasm.LineSpacingType.AtLeast;
break;
}
case "auto": {
kind = wasm.LineSpacingType.Auto;
break;
}
case "exact": {
kind = wasm.LineSpacingType.Exact;
break;
}
}
let spacing = wasm.createLineSpacing();
if (lineSpacing._before != null) {
spacing = spacing.before(lineSpacing._before);
}
if (lineSpacing._after != null) {
spacing = spacing.after(lineSpacing._after);
}
if (lineSpacing._beforeLines != null) {
spacing = spacing.before_lines(lineSpacing._beforeLines);
}
if (lineSpacing._afterLines != null) {
spacing = spacing.after_lines(lineSpacing._afterLines);
}
if (lineSpacing._line != null) {
spacing = spacing.line(lineSpacing._line);
}
if (kind != null) {
spacing = spacing.line_rule(kind);
}
return spacing;
}
buildParagraph(p: Paragraph) { buildParagraph(p: Paragraph) {
let paragraph = wasm.createParagraph(); let paragraph = wasm.createParagraph();
p.children.forEach((child) => { p.children.forEach((child) => {
@ -424,28 +469,10 @@ export class Docx {
} }
if (typeof p.property.lineSpacing !== "undefined") { if (typeof p.property.lineSpacing !== "undefined") {
const { lineSpacing } = p.property; const spacing = this.buildLineSpacing(p.property);
let kind; if (spacing) {
switch (p.property.lineSpacing.lineRule) { paragraph = paragraph.line_spacing(spacing);
case "atLeast": {
kind = wasm.LineSpacingType.AtLeast;
break;
} }
case "auto": {
kind = wasm.LineSpacingType.Auto;
break;
}
case "exact": {
kind = wasm.LineSpacingType.Exact;
break;
}
}
paragraph = paragraph.line_spacing(
lineSpacing.before,
lineSpacing.after,
lineSpacing.line,
kind
);
} }
if (p.property.runProperty.italic) { if (p.property.runProperty.italic) {

View File

@ -26,7 +26,41 @@ export type AlignmentType =
export type SpecialIndentKind = "firstLine" | "hanging"; export type SpecialIndentKind = "firstLine" | "hanging";
export type LineSpacingType = "atLeast" | "auto" | "exact" export type LineSpacingType = "atLeast" | "auto" | "exact";
export class LineSpacing {
_before?: number;
_after?: number;
_beforeLines?: number;
_afterLines?: number;
_line?: number;
_lineRule?: LineSpacingType;
before(v: number) {
this._before = v;
return this;
}
after(v: number) {
this._after = v;
return this;
}
beforeLines(v: number) {
this._beforeLines = v;
return this;
}
afterLines(v: number) {
this._afterLines = v;
return this;
}
line(v: number) {
this._line = v;
return this;
}
lineRule(v: LineSpacingType) {
this._lineRule = v;
return this;
}
}
export type ParagraphProperty = { export type ParagraphProperty = {
align?: AlignmentType; align?: AlignmentType;
@ -40,12 +74,7 @@ export type ParagraphProperty = {
id: number; id: number;
level: number; level: number;
}; };
lineSpacing?: { lineSpacing?: LineSpacing;
before?: number;
after?: number;
line?: number;
lineRule?: LineSpacingType;
};
runProperty: RunProperty; runProperty: RunProperty;
keepNext: boolean; keepNext: boolean;
keepLines: boolean; keepLines: boolean;
@ -128,13 +157,8 @@ export class Paragraph {
return this; return this;
} }
lineSpacing(before?: number, after?: number, line?: number, lineRule?: LineSpacingType) { lineSpacing(spacing: LineSpacing) {
this.property.lineSpacing = { this.property.lineSpacing = spacing;
before,
after,
line,
lineRule
}
return this; return this;
} }

View File

@ -7,6 +7,7 @@ mod footer;
mod insert; mod insert;
mod level; mod level;
mod level_override; mod level_override;
mod line_spacing;
mod numbering; mod numbering;
mod page_margin; mod page_margin;
mod paragraph; mod paragraph;
@ -28,6 +29,7 @@ pub use footer::*;
pub use insert::*; pub use insert::*;
pub use level::*; pub use level::*;
pub use level_override::*; pub use level_override::*;
pub use line_spacing::*;
pub use numbering::*; pub use numbering::*;
pub use page_margin::*; pub use page_margin::*;
pub use paragraph::*; pub use paragraph::*;

View File

@ -0,0 +1,49 @@
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
#[derive(Debug)]
pub struct LineSpacing(docx_rs::LineSpacing);
#[wasm_bindgen(js_name = createLineSpacing)]
pub fn create_line_spacing() -> LineSpacing {
LineSpacing(docx_rs::LineSpacing::new())
}
impl LineSpacing {
pub fn take(self) -> docx_rs::LineSpacing {
self.0
}
}
#[wasm_bindgen]
impl LineSpacing {
pub fn line_rule(mut self, t: docx_rs::LineSpacingType) -> Self {
self.0 = self.0.line_rule(t);
self
}
pub fn before(mut self, before: u32) -> Self {
self.0 = self.0.before(before);
self
}
pub fn after(mut self, after: u32) -> Self {
self.0 = self.0.after(after);
self
}
pub fn before_lines(mut self, before: u32) -> Self {
self.0 = self.0.before_lines(before);
self
}
pub fn after_lines(mut self, after: u32) -> Self {
self.0 = self.0.after_lines(after);
self
}
pub fn line(mut self, line: u32) -> Self {
self.0 = self.0.line(line);
self
}
}

View File

@ -112,14 +112,8 @@ impl Paragraph {
self self
} }
pub fn line_spacing( pub fn line_spacing(mut self, spacing: LineSpacing) -> Self {
mut self, self.0 = self.0.line_spacing(spacing.take());
before: Option<u32>,
after: Option<u32>,
line: Option<u32>,
spacing_type: Option<docx_rs::LineSpacingType>,
) -> Self {
self.0 = self.0.line_spacing(before, after, line, spacing_type);
self self
} }

File diff suppressed because it is too large Load Diff

View File

@ -74,6 +74,12 @@ describe("reader", () => {
const json = w.readDocx(buffer); const json = w.readDocx(buffer);
expect(json).toMatchSnapshot(); expect(json).toMatchSnapshot();
}); });
test("should read afterLines docx", () => {
const buffer = readFileSync("../fixtures/after_lines/after_lines.docx");
const json = w.readDocx(buffer);
expect(json).toMatchSnapshot();
});
}); });
describe("writer", () => { describe("writer", () => {
@ -352,7 +358,9 @@ describe("writer", () => {
test("should write line spacing", () => { test("should write line spacing", () => {
const p = new w.Paragraph() const p = new w.Paragraph()
.addRun(new w.Run().addText("Hello ")) .addRun(new w.Run().addText("Hello "))
.lineSpacing(100, "", 100, 1); .lineSpacing(
new w.LineSpacing().before(100).after(0).line(100).afterLines(400)
);
const buffer = new w.Docx().addParagraph(p).build(); const buffer = new w.Docx().addParagraph(p).build();
writeFileSync("../output/line_spacing.docx", buffer); writeFileSync("../output/line_spacing.docx", buffer);
const z = new Zip(Buffer.from(buffer)); const z = new Zip(Buffer.from(buffer));

Binary file not shown.

1252
test.json

File diff suppressed because it is too large Load Diff