Improve spacing (#367)
* fix: line spacing interfaces * fix: js interface * fix: js if * fix: reader and testingmain
parent
f1044c887c
commit
c6a555aa75
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
|
||||||
self.before = before;
|
pub fn line_rule(mut self, t: LineSpacingType) -> Self {
|
||||||
|
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,8 +60,15 @@ 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(
|
||||||
.build()
|
self.before,
|
||||||
|
self.after,
|
||||||
|
self.line,
|
||||||
|
self.before_lines,
|
||||||
|
self.after_lines,
|
||||||
|
self.line_rule,
|
||||||
|
)
|
||||||
|
.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!(
|
||||||
|
|
|
@ -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!(
|
||||||
|
|
|
@ -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>"#
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 => {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)?;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -1,181 +1,205 @@
|
||||||
import {Run, RunProperty, RunFonts, createDefaultRunProperty} from "./run";
|
import { Run, RunProperty, RunFonts, createDefaultRunProperty } from "./run";
|
||||||
import {Insert} from "./insert";
|
import { Insert } from "./insert";
|
||||||
import {Delete} from "./delete";
|
import { Delete } from "./delete";
|
||||||
import {BookmarkStart} from "./bookmark-start";
|
import { BookmarkStart } from "./bookmark-start";
|
||||||
import {BookmarkEnd} from "./bookmark-end";
|
import { BookmarkEnd } from "./bookmark-end";
|
||||||
import {Comment} from "./comment";
|
import { Comment } from "./comment";
|
||||||
import {CommentEnd} from "./comment-end";
|
import { CommentEnd } from "./comment-end";
|
||||||
|
|
||||||
export type ParagraphChild =
|
export type ParagraphChild =
|
||||||
| Run
|
| Run
|
||||||
| Insert
|
| Insert
|
||||||
| Delete
|
| Delete
|
||||||
| BookmarkStart
|
| BookmarkStart
|
||||||
| BookmarkEnd
|
| BookmarkEnd
|
||||||
| Comment
|
| Comment
|
||||||
| CommentEnd;
|
| CommentEnd;
|
||||||
|
|
||||||
export type AlignmentType =
|
export type AlignmentType =
|
||||||
| "center"
|
| "center"
|
||||||
| "left"
|
| "left"
|
||||||
| "right"
|
| "right"
|
||||||
| "both"
|
| "both"
|
||||||
| "justified"
|
| "justified"
|
||||||
| "distribute"
|
| "distribute"
|
||||||
| "end";
|
| "end";
|
||||||
|
|
||||||
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;
|
||||||
styleId?: string;
|
styleId?: string;
|
||||||
indent?: {
|
indent?: {
|
||||||
left: number;
|
left: number;
|
||||||
specialIndentKind?: SpecialIndentKind;
|
specialIndentKind?: SpecialIndentKind;
|
||||||
specialIndentSize?: number;
|
specialIndentSize?: number;
|
||||||
};
|
};
|
||||||
numbering?: {
|
numbering?: {
|
||||||
id: number;
|
id: number;
|
||||||
level: number;
|
level: number;
|
||||||
};
|
};
|
||||||
lineSpacing?: {
|
lineSpacing?: LineSpacing;
|
||||||
before?: number;
|
runProperty: RunProperty;
|
||||||
after?: number;
|
keepNext: boolean;
|
||||||
line?: number;
|
keepLines: boolean;
|
||||||
lineRule?: LineSpacingType;
|
pageBreakBefore: boolean;
|
||||||
};
|
windowControl: boolean;
|
||||||
runProperty: RunProperty;
|
|
||||||
keepNext: boolean;
|
|
||||||
keepLines: boolean;
|
|
||||||
pageBreakBefore: boolean;
|
|
||||||
windowControl: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createDefaultParagraphProperty = (): ParagraphProperty => {
|
export const createDefaultParagraphProperty = (): ParagraphProperty => {
|
||||||
return {
|
return {
|
||||||
runProperty: createDefaultRunProperty(),
|
runProperty: createDefaultRunProperty(),
|
||||||
keepNext: false,
|
keepNext: false,
|
||||||
keepLines: false,
|
keepLines: false,
|
||||||
pageBreakBefore: false,
|
pageBreakBefore: false,
|
||||||
windowControl: false,
|
windowControl: false,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Paragraph {
|
export class Paragraph {
|
||||||
hasNumberings = false;
|
hasNumberings = false;
|
||||||
children: ParagraphChild[] = [];
|
children: ParagraphChild[] = [];
|
||||||
property: ParagraphProperty = createDefaultParagraphProperty();
|
property: ParagraphProperty = createDefaultParagraphProperty();
|
||||||
|
|
||||||
addRun(run: Run) {
|
addRun(run: Run) {
|
||||||
this.children.push(run);
|
this.children.push(run);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
addInsert(ins: Insert) {
|
addInsert(ins: Insert) {
|
||||||
this.children.push(ins);
|
this.children.push(ins);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
addDelete(del: Delete) {
|
addDelete(del: Delete) {
|
||||||
this.children.push(del);
|
this.children.push(del);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
addBookmarkStart(id: number, name: string) {
|
addBookmarkStart(id: number, name: string) {
|
||||||
this.children.push(new BookmarkStart(id, name));
|
this.children.push(new BookmarkStart(id, name));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
addBookmarkEnd(id: number) {
|
addBookmarkEnd(id: number) {
|
||||||
this.children.push(new BookmarkEnd(id));
|
this.children.push(new BookmarkEnd(id));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
addCommentStart(comment: Comment) {
|
addCommentStart(comment: Comment) {
|
||||||
this.children.push(comment);
|
this.children.push(comment);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
addCommentEnd(end: CommentEnd) {
|
addCommentEnd(end: CommentEnd) {
|
||||||
this.children.push(end);
|
this.children.push(end);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
align(type: AlignmentType) {
|
align(type: AlignmentType) {
|
||||||
this.property.align = type;
|
this.property.align = type;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
style(id: string) {
|
style(id: string) {
|
||||||
this.property.styleId = id;
|
this.property.styleId = id;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
indent(
|
indent(
|
||||||
left: number,
|
left: number,
|
||||||
specialIndentKind?: SpecialIndentKind,
|
specialIndentKind?: SpecialIndentKind,
|
||||||
specialIndentSize?: number
|
specialIndentSize?: number
|
||||||
) {
|
) {
|
||||||
this.property.indent = {left, specialIndentKind, specialIndentSize};
|
this.property.indent = { left, specialIndentKind, specialIndentSize };
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
numbering(id: number, level: number) {
|
numbering(id: number, level: number) {
|
||||||
this.hasNumberings = true;
|
this.hasNumberings = true;
|
||||||
this.property.numbering = {id, level};
|
this.property.numbering = { id, level };
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
lineSpacing(before?: number, after?: number, line?: number, lineRule?: LineSpacingType) {
|
lineSpacing(spacing: LineSpacing) {
|
||||||
this.property.lineSpacing = {
|
this.property.lineSpacing = spacing;
|
||||||
before,
|
return this;
|
||||||
after,
|
}
|
||||||
line,
|
|
||||||
lineRule
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
keepNext(v: boolean) {
|
keepNext(v: boolean) {
|
||||||
this.property = {...this.property, keepNext: v};
|
this.property = { ...this.property, keepNext: v };
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
keepLines(v: boolean) {
|
keepLines(v: boolean) {
|
||||||
this.property = {...this.property, keepLines: v};
|
this.property = { ...this.property, keepLines: v };
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
pageBreakBefore(v: boolean) {
|
pageBreakBefore(v: boolean) {
|
||||||
this.property = {...this.property, pageBreakBefore: v};
|
this.property = { ...this.property, pageBreakBefore: v };
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
windowControl(v: boolean) {
|
windowControl(v: boolean) {
|
||||||
this.property = {...this.property, windowControl: v};
|
this.property = { ...this.property, windowControl: v };
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// run property
|
// run property
|
||||||
size(size: number) {
|
size(size: number) {
|
||||||
this.property.runProperty = {...this.property.runProperty, size};
|
this.property.runProperty = { ...this.property.runProperty, size };
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
bold() {
|
bold() {
|
||||||
this.property.runProperty = {...this.property.runProperty, bold: true};
|
this.property.runProperty = { ...this.property.runProperty, bold: true };
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
italic() {
|
italic() {
|
||||||
this.property.runProperty = {...this.property.runProperty, italic: true};
|
this.property.runProperty = { ...this.property.runProperty, italic: true };
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
fonts(fonts: RunFonts) {
|
fonts(fonts: RunFonts) {
|
||||||
this.property.runProperty = {...this.property.runProperty, fonts};
|
this.property.runProperty = { ...this.property.runProperty, fonts };
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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::*;
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
@ -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.
Loading…
Reference in New Issue