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};
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![];
file.read_to_end(&mut buf).unwrap();

View File

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

View File

@ -244,16 +244,8 @@ impl Paragraph {
self
}
pub fn line_spacing(
mut self,
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);
pub fn line_spacing(mut self, spacing: LineSpacing) -> Self {
self.property = self.property.line_spacing(spacing);
self
}
}
@ -334,8 +326,13 @@ mod tests {
#[test]
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()
.line_spacing(Some(20), Some(30), Some(200), Some(LineSpacingType::Auto))
.line_spacing(spacing)
.add_run(Run::new().add_text("Hello"))
.build();
assert_eq!(

View File

@ -2,7 +2,7 @@ use serde::Serialize;
use super::*;
use crate::documents::BuildXML;
use crate::types::{AlignmentType, LineSpacingType, SpecialIndentType};
use crate::types::{AlignmentType, SpecialIndentType};
use crate::xml_builder::*;
#[derive(Serialize, Debug, Clone, PartialEq)]
@ -78,19 +78,8 @@ impl ParagraphProperty {
self
}
pub fn line_spacing(
mut self,
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),
);
pub fn line_spacing(mut self, spacing: LineSpacing) -> Self {
self.line_spacing = Some(spacing);
self
}
@ -173,6 +162,7 @@ impl BuildXML for ParagraphProperty {
mod tests {
use super::*;
use crate::types::LineSpacingType;
#[cfg(test)]
use pretty_assertions::assert_eq;
use std::str;
@ -238,9 +228,10 @@ mod tests {
#[test]
fn test_line_spacing() {
let props = ParagraphProperty::new();
let bytes = props
.line_spacing(None, None, Some(100), Some(LineSpacingType::AtLeast))
.build();
let spacing = LineSpacing::new()
.line_rule(LineSpacingType::AtLeast)
.line(100);
let bytes = props.line_spacing(spacing).build();
assert_eq!(
str::from_utf8(&bytes).unwrap(),
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::LineSpacing;
use crate::ReaderError;
use std::str::FromStr;
use xml::attribute::OwnedAttribute;
pub type LineSpacingResult = Result<
(
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;
pub fn read_line_spacing(attributes: &[OwnedAttribute]) -> Result<LineSpacing, ReaderError> {
let mut spacing = LineSpacing::new();
for a in attributes {
let local_name = &a.name.local_name;
if local_name == "before" {
before = Some(u32::from_str(&a.value)?);
} else if local_name == "after" {
after = Some(u32::from_str(&a.value)?);
} else if local_name == "line" {
line = Some(u32::from_str(&a.value)?);
} else if local_name == "lineRule" {
spacing_type = Some(LineSpacingType::from_str(&a.value)?);
match local_name.as_str() {
"before" => {
spacing = spacing.before(u32::from_str(&a.value)?);
}
"after" => {
spacing = spacing.after(u32::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;
}
XMLElement::Spacing => {
let (before, after, line, spacing_type) =
attributes::line_spacing::read_line_spacing(&attributes)?;
p = p.line_spacing(before, after, line, spacing_type);
if let Ok(spacing) =
attributes::line_spacing::read_line_spacing(&attributes)
{
p = p.line_spacing(spacing);
}
continue;
}
XMLElement::Justification => {

View File

@ -167,11 +167,15 @@ impl XMLBuilder {
before: Option<u32>,
after: Option<u32>,
line: Option<u32>,
before_lines: Option<u32>,
after_lines: Option<u32>,
spacing: Option<LineSpacingType>,
) -> Self {
let mut xml_event = XmlEvent::start_element("w:spacing");
let before_val: String;
let after_val: String;
let before_lines_val: String;
let after_lines_val: String;
let line_val: String;
if let Some(before) = before {
@ -182,6 +186,14 @@ impl XMLBuilder {
after_val = format!("{}", after);
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 {
line_val = format!("{}", line);
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> {
let path = std::path::Path::new("./tests/output/line_spacing.docx");
let file = std::fs::File::create(&path).unwrap();
Docx::new()
.add_paragraph(
Paragraph::new()
.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(
Paragraph::new()
.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(
Paragraph::new()
.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()
.pack(file)?;

View File

@ -1,4 +1,4 @@
import { Paragraph } from "./paragraph";
import { Paragraph, ParagraphProperty } from "./paragraph";
import { Insert } from "./insert";
import { Delete } from "./delete";
import { DeleteText } from "./delete-text";
@ -339,6 +339,51 @@ export class Docx {
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) {
let paragraph = wasm.createParagraph();
p.children.forEach((child) => {
@ -424,28 +469,10 @@ export class Docx {
}
if (typeof p.property.lineSpacing !== "undefined") {
const { lineSpacing } = p.property;
let kind;
switch (p.property.lineSpacing.lineRule) {
case "atLeast": {
kind = wasm.LineSpacingType.AtLeast;
break;
}
case "auto": {
kind = wasm.LineSpacingType.Auto;
break;
}
case "exact": {
kind = wasm.LineSpacingType.Exact;
break;
}
const spacing = this.buildLineSpacing(p.property);
if (spacing) {
paragraph = paragraph.line_spacing(spacing);
}
paragraph = paragraph.line_spacing(
lineSpacing.before,
lineSpacing.after,
lineSpacing.line,
kind
);
}
if (p.property.runProperty.italic) {

View File

@ -1,181 +1,205 @@
import {Run, RunProperty, RunFonts, createDefaultRunProperty} from "./run";
import {Insert} from "./insert";
import {Delete} from "./delete";
import {BookmarkStart} from "./bookmark-start";
import {BookmarkEnd} from "./bookmark-end";
import {Comment} from "./comment";
import {CommentEnd} from "./comment-end";
import { Run, RunProperty, RunFonts, createDefaultRunProperty } from "./run";
import { Insert } from "./insert";
import { Delete } from "./delete";
import { BookmarkStart } from "./bookmark-start";
import { BookmarkEnd } from "./bookmark-end";
import { Comment } from "./comment";
import { CommentEnd } from "./comment-end";
export type ParagraphChild =
| Run
| Insert
| Delete
| BookmarkStart
| BookmarkEnd
| Comment
| CommentEnd;
| Run
| Insert
| Delete
| BookmarkStart
| BookmarkEnd
| Comment
| CommentEnd;
export type AlignmentType =
| "center"
| "left"
| "right"
| "both"
| "justified"
| "distribute"
| "end";
| "center"
| "left"
| "right"
| "both"
| "justified"
| "distribute"
| "end";
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 = {
align?: AlignmentType;
styleId?: string;
indent?: {
left: number;
specialIndentKind?: SpecialIndentKind;
specialIndentSize?: number;
};
numbering?: {
id: number;
level: number;
};
lineSpacing?: {
before?: number;
after?: number;
line?: number;
lineRule?: LineSpacingType;
};
runProperty: RunProperty;
keepNext: boolean;
keepLines: boolean;
pageBreakBefore: boolean;
windowControl: boolean;
align?: AlignmentType;
styleId?: string;
indent?: {
left: number;
specialIndentKind?: SpecialIndentKind;
specialIndentSize?: number;
};
numbering?: {
id: number;
level: number;
};
lineSpacing?: LineSpacing;
runProperty: RunProperty;
keepNext: boolean;
keepLines: boolean;
pageBreakBefore: boolean;
windowControl: boolean;
};
export const createDefaultParagraphProperty = (): ParagraphProperty => {
return {
runProperty: createDefaultRunProperty(),
keepNext: false,
keepLines: false,
pageBreakBefore: false,
windowControl: false,
};
return {
runProperty: createDefaultRunProperty(),
keepNext: false,
keepLines: false,
pageBreakBefore: false,
windowControl: false,
};
};
export class Paragraph {
hasNumberings = false;
children: ParagraphChild[] = [];
property: ParagraphProperty = createDefaultParagraphProperty();
hasNumberings = false;
children: ParagraphChild[] = [];
property: ParagraphProperty = createDefaultParagraphProperty();
addRun(run: Run) {
this.children.push(run);
return this;
}
addRun(run: Run) {
this.children.push(run);
return this;
}
addInsert(ins: Insert) {
this.children.push(ins);
return this;
}
addInsert(ins: Insert) {
this.children.push(ins);
return this;
}
addDelete(del: Delete) {
this.children.push(del);
return this;
}
addDelete(del: Delete) {
this.children.push(del);
return this;
}
addBookmarkStart(id: number, name: string) {
this.children.push(new BookmarkStart(id, name));
return this;
}
addBookmarkStart(id: number, name: string) {
this.children.push(new BookmarkStart(id, name));
return this;
}
addBookmarkEnd(id: number) {
this.children.push(new BookmarkEnd(id));
return this;
}
addBookmarkEnd(id: number) {
this.children.push(new BookmarkEnd(id));
return this;
}
addCommentStart(comment: Comment) {
this.children.push(comment);
return this;
}
addCommentStart(comment: Comment) {
this.children.push(comment);
return this;
}
addCommentEnd(end: CommentEnd) {
this.children.push(end);
return this;
}
addCommentEnd(end: CommentEnd) {
this.children.push(end);
return this;
}
align(type: AlignmentType) {
this.property.align = type;
return this;
}
align(type: AlignmentType) {
this.property.align = type;
return this;
}
style(id: string) {
this.property.styleId = id;
return this;
}
style(id: string) {
this.property.styleId = id;
return this;
}
indent(
left: number,
specialIndentKind?: SpecialIndentKind,
specialIndentSize?: number
) {
this.property.indent = {left, specialIndentKind, specialIndentSize};
return this;
}
indent(
left: number,
specialIndentKind?: SpecialIndentKind,
specialIndentSize?: number
) {
this.property.indent = { left, specialIndentKind, specialIndentSize };
return this;
}
numbering(id: number, level: number) {
this.hasNumberings = true;
this.property.numbering = {id, level};
return this;
}
numbering(id: number, level: number) {
this.hasNumberings = true;
this.property.numbering = { id, level };
return this;
}
lineSpacing(before?: number, after?: number, line?: number, lineRule?: LineSpacingType) {
this.property.lineSpacing = {
before,
after,
line,
lineRule
}
return this;
}
lineSpacing(spacing: LineSpacing) {
this.property.lineSpacing = spacing;
return this;
}
keepNext(v: boolean) {
this.property = {...this.property, keepNext: v};
return this;
}
keepNext(v: boolean) {
this.property = { ...this.property, keepNext: v };
return this;
}
keepLines(v: boolean) {
this.property = {...this.property, keepLines: v};
return this;
}
keepLines(v: boolean) {
this.property = { ...this.property, keepLines: v };
return this;
}
pageBreakBefore(v: boolean) {
this.property = {...this.property, pageBreakBefore: v};
return this;
}
pageBreakBefore(v: boolean) {
this.property = { ...this.property, pageBreakBefore: v };
return this;
}
windowControl(v: boolean) {
this.property = {...this.property, windowControl: v};
return this;
}
windowControl(v: boolean) {
this.property = { ...this.property, windowControl: v };
return this;
}
// run property
size(size: number) {
this.property.runProperty = {...this.property.runProperty, size};
return this;
}
// run property
size(size: number) {
this.property.runProperty = { ...this.property.runProperty, size };
return this;
}
bold() {
this.property.runProperty = {...this.property.runProperty, bold: true};
return this;
}
bold() {
this.property.runProperty = { ...this.property.runProperty, bold: true };
return this;
}
italic() {
this.property.runProperty = {...this.property.runProperty, italic: true};
return this;
}
italic() {
this.property.runProperty = { ...this.property.runProperty, italic: true };
return this;
}
fonts(fonts: RunFonts) {
this.property.runProperty = {...this.property.runProperty, fonts};
return this;
}
fonts(fonts: RunFonts) {
this.property.runProperty = { ...this.property.runProperty, fonts };
return this;
}
}

View File

@ -7,6 +7,7 @@ mod footer;
mod insert;
mod level;
mod level_override;
mod line_spacing;
mod numbering;
mod page_margin;
mod paragraph;
@ -28,6 +29,7 @@ pub use footer::*;
pub use insert::*;
pub use level::*;
pub use level_override::*;
pub use line_spacing::*;
pub use numbering::*;
pub use page_margin::*;
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
}
pub fn line_spacing(
mut self,
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);
pub fn line_spacing(mut self, spacing: LineSpacing) -> Self {
self.0 = self.0.line_spacing(spacing.take());
self
}

File diff suppressed because it is too large Load Diff

View File

@ -74,6 +74,12 @@ describe("reader", () => {
const json = w.readDocx(buffer);
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", () => {
@ -352,7 +358,9 @@ describe("writer", () => {
test("should write line spacing", () => {
const p = new w.Paragraph()
.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();
writeFileSync("../output/line_spacing.docx", 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