Support text border (#257)

* feat: Add text_border

* feat: Add reader

* feat: Add js if and test

* 0.0.171

* update test
main
bokuweb 2021-03-21 00:16:43 +09:00 committed by GitHub
parent ba0ef0bd48
commit 6f6a399597
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 516 additions and 77 deletions

View File

@ -99,6 +99,7 @@ writeFileSync("hello.docx", buffer);
- [x] Underline
- [x] vanish
- [x] Italic
- [x] TextBorder
- [x] Break
- [x] Header
- [ ] Footer

View File

@ -59,7 +59,7 @@ mod tests {
);
assert_eq!(
serde_json::to_string(&graphic).unwrap(),
r#"{"children":[{"dataType":"wpShape","children":[{"type":"shape","data":{"children":[{"type":"textbox","data":{"children":[{"children":[{"type":"paragraph","data":{"id":"12345678","children":[{"type":"run","data":{"runProperty":{"sz":null,"szCs":null,"color":null,"highlight":null,"underline":null,"bold":null,"boldCs":null,"italic":null,"italicCs":null,"vanish":null,"spacing":null,"fonts":null},"children":[{"type":"text","data":{"preserveSpace":true,"text":"pattern1"}}]}}],"property":{"runProperty":{"sz":null,"szCs":null,"color":null,"highlight":null,"underline":null,"bold":null,"boldCs":null,"italic":null,"italicCs":null,"vanish":null,"spacing":null,"fonts":null},"style":null,"numberingProperty":null,"alignment":null,"indent":null,"lineHeight":null},"hasNumbering":false}}],"has_numbering":false}],"hasNumbering":false}}]}}]}]}"#
r#"{"children":[{"dataType":"wpShape","children":[{"type":"shape","data":{"children":[{"type":"textbox","data":{"children":[{"children":[{"type":"paragraph","data":{"id":"12345678","children":[{"type":"run","data":{"runProperty":{"sz":null,"szCs":null,"color":null,"highlight":null,"underline":null,"bold":null,"boldCs":null,"italic":null,"italicCs":null,"vanish":null,"spacing":null,"fonts":null,"textBorder":null},"children":[{"type":"text","data":{"preserveSpace":true,"text":"pattern1"}}]}}],"property":{"runProperty":{"sz":null,"szCs":null,"color":null,"highlight":null,"underline":null,"bold":null,"boldCs":null,"italic":null,"italicCs":null,"vanish":null,"spacing":null,"fonts":null,"textBorder":null},"style":null,"numberingProperty":null,"alignment":null,"indent":null,"lineHeight":null},"hasNumbering":false}}],"has_numbering":false}],"hasNumbering":false}}]}}]}]}"#
);
}
}

View File

@ -90,7 +90,7 @@ mod tests {
.num_style_link("style1");
assert_eq!(
serde_json::to_string(&c).unwrap(),
r#"{"id":0,"styleLink":null,"numStyleLink":"style1","levels":[{"level":1,"start":1,"format":"decimal","text":"%4.","jc":"left","pstyle":null,"paragraphProperty":{"runProperty":{"sz":null,"szCs":null,"color":null,"highlight":null,"underline":null,"bold":null,"boldCs":null,"italic":null,"italicCs":null,"vanish":null,"spacing":null,"fonts":null},"style":null,"numberingProperty":null,"alignment":null,"indent":null,"lineHeight":null},"runProperty":{"sz":null,"szCs":null,"color":null,"highlight":null,"underline":null,"bold":null,"boldCs":null,"italic":null,"italicCs":null,"vanish":null,"spacing":null,"fonts":null},"suffix":"tab"}]}"#
r#"{"id":0,"styleLink":null,"numStyleLink":"style1","levels":[{"level":1,"start":1,"format":"decimal","text":"%4.","jc":"left","pstyle":null,"paragraphProperty":{"runProperty":{"sz":null,"szCs":null,"color":null,"highlight":null,"underline":null,"bold":null,"boldCs":null,"italic":null,"italicCs":null,"vanish":null,"spacing":null,"fonts":null,"textBorder":null},"style":null,"numberingProperty":null,"alignment":null,"indent":null,"lineHeight":null},"runProperty":{"sz":null,"szCs":null,"color":null,"highlight":null,"underline":null,"bold":null,"boldCs":null,"italic":null,"italicCs":null,"vanish":null,"spacing":null,"fonts":null,"textBorder":null},"suffix":"tab"}]}"#
);
}
}

View File

@ -76,6 +76,7 @@ mod table_row_property;
mod table_style;
mod table_width;
mod text;
mod text_border;
mod text_box_content;
mod text_direction;
mod underline;
@ -165,6 +166,7 @@ pub use table_row_property::*;
pub use table_style::*;
pub use table_width::*;
pub use text::*;
pub use text_border::*;
pub use text_box_content::*;
pub use text_direction::*;
pub use underline::*;

View File

@ -295,7 +295,7 @@ mod tests {
let p = Paragraph::new().add_run(run);
assert_eq!(
serde_json::to_string(&p).unwrap(),
r#"{"id":"12345678","children":[{"type":"run","data":{"runProperty":{"sz":null,"szCs":null,"color":null,"highlight":null,"underline":null,"bold":null,"boldCs":null,"italic":null,"italicCs":null,"vanish":null,"spacing":null,"fonts":null},"children":[{"type":"text","data":{"preserveSpace":true,"text":"Hello"}}]}}],"property":{"runProperty":{"sz":null,"szCs":null,"color":null,"highlight":null,"underline":null,"bold":null,"boldCs":null,"italic":null,"italicCs":null,"vanish":null,"spacing":null,"fonts":null},"style":null,"numberingProperty":null,"alignment":null,"indent":null,"lineHeight":null},"hasNumbering":false}"#
r#"{"id":"12345678","children":[{"type":"run","data":{"runProperty":{"sz":null,"szCs":null,"color":null,"highlight":null,"underline":null,"bold":null,"boldCs":null,"italic":null,"italicCs":null,"vanish":null,"spacing":null,"fonts":null,"textBorder":null},"children":[{"type":"text","data":{"preserveSpace":true,"text":"Hello"}}]}}],"property":{"runProperty":{"sz":null,"szCs":null,"color":null,"highlight":null,"underline":null,"bold":null,"boldCs":null,"italic":null,"italicCs":null,"vanish":null,"spacing":null,"fonts":null,"textBorder":null},"style":null,"numberingProperty":null,"alignment":null,"indent":null,"lineHeight":null},"hasNumbering":false}"#
);
}
@ -306,7 +306,7 @@ mod tests {
let p = Paragraph::new().add_insert(ins);
assert_eq!(
serde_json::to_string(&p).unwrap(),
r#"{"id":"12345678","children":[{"type":"insert","data":{"children":[{"type":"run","data":{"runProperty":{"sz":null,"szCs":null,"color":null,"highlight":null,"underline":null,"bold":null,"boldCs":null,"italic":null,"italicCs":null,"vanish":null,"spacing":null,"fonts":null},"children":[{"type":"text","data":{"preserveSpace":true,"text":"Hello"}}]}}],"author":"unnamed","date":"1970-01-01T00:00:00Z"}}],"property":{"runProperty":{"sz":null,"szCs":null,"color":null,"highlight":null,"underline":null,"bold":null,"boldCs":null,"italic":null,"italicCs":null,"vanish":null,"spacing":null,"fonts":null},"style":null,"numberingProperty":null,"alignment":null,"indent":null,"lineHeight":null},"hasNumbering":false}"#
r#"{"id":"12345678","children":[{"type":"insert","data":{"children":[{"type":"run","data":{"runProperty":{"sz":null,"szCs":null,"color":null,"highlight":null,"underline":null,"bold":null,"boldCs":null,"italic":null,"italicCs":null,"vanish":null,"spacing":null,"fonts":null,"textBorder":null},"children":[{"type":"text","data":{"preserveSpace":true,"text":"Hello"}}]}}],"author":"unnamed","date":"1970-01-01T00:00:00Z"}}],"property":{"runProperty":{"sz":null,"szCs":null,"color":null,"highlight":null,"underline":null,"bold":null,"boldCs":null,"italic":null,"italicCs":null,"vanish":null,"spacing":null,"fonts":null,"textBorder":null},"style":null,"numberingProperty":null,"alignment":null,"indent":null,"lineHeight":null},"hasNumbering":false}"#
);
}
}

View File

@ -132,7 +132,7 @@ mod tests {
let b = c.indent(Some(20), Some(SpecialIndentType::FirstLine(10)), None, None);
assert_eq!(
serde_json::to_string(&b).unwrap(),
r#"{"runProperty":{"sz":null,"szCs":null,"color":null,"highlight":null,"underline":null,"bold":null,"boldCs":null,"italic":null,"italicCs":null,"vanish":null,"spacing":null,"fonts":null},"style":null,"numberingProperty":null,"alignment":null,"indent":{"start":20,"startChars":null,"end":null,"specialIndent":{"type":"firstLine","val":10}},"lineHeight":null}"#
r#"{"runProperty":{"sz":null,"szCs":null,"color":null,"highlight":null,"underline":null,"bold":null,"boldCs":null,"italic":null,"italicCs":null,"vanish":null,"spacing":null,"fonts":null,"textBorder":null},"style":null,"numberingProperty":null,"alignment":null,"indent":{"start":20,"startChars":null,"end":null,"specialIndent":{"type":"firstLine","val":10}},"lineHeight":null}"#
);
}
}

View File

@ -269,11 +269,12 @@ mod tests {
vanish: Some(Vanish::new()),
spacing: Some(100),
fonts: None,
text_border: None,
},
};
assert_eq!(
serde_json::to_string(&run).unwrap(),
r#"{"runProperty":{"sz":30,"szCs":30,"color":"C9211E","highlight":"yellow","underline":"single","bold":true,"boldCs":true,"italic":true,"italicCs":true,"vanish":true,"spacing":100,"fonts":null},"children":[{"type":"tab"},{"type":"text","data":{"preserveSpace":true,"text":"Hello"}},{"type":"break","data":{"breakType":"page"}},{"type":"deleteText","data":{"text":"deleted","preserveSpace":true}}]}"#
r#"{"runProperty":{"sz":30,"szCs":30,"color":"C9211E","highlight":"yellow","underline":"single","bold":true,"boldCs":true,"italic":true,"italicCs":true,"vanish":true,"spacing":100,"fonts":null,"textBorder":null},"children":[{"type":"tab"},{"type":"text","data":{"preserveSpace":true,"text":"Hello"}},{"type":"break","data":{"breakType":"page"}},{"type":"deleteText","data":{"text":"deleted","preserveSpace":true}}]}"#
);
}
}

View File

@ -19,6 +19,7 @@ pub struct RunProperty {
pub vanish: Option<Vanish>,
pub spacing: Option<i32>,
pub fonts: Option<RunFonts>,
pub text_border: Option<TextBorder>,
}
impl RunProperty {
@ -85,6 +86,11 @@ impl RunProperty {
self.fonts = Some(font);
self
}
pub fn text_border(mut self, b: TextBorder) -> Self {
self.text_border = Some(b);
self
}
}
impl Default for RunProperty {
@ -102,6 +108,7 @@ impl Default for RunProperty {
vanish: None,
fonts: None,
spacing: None,
text_border: None,
}
}
}
@ -126,6 +133,7 @@ impl BuildXML for RunProperty {
.add_optional_child(&self.underline)
.add_optional_child(&self.vanish)
.add_optional_child(&self.fonts)
.add_optional_child(&self.text_border)
.add_optional_child(&spacing)
.close()
.build()

View File

@ -185,7 +185,7 @@ mod tests {
.grid_span(2);
assert_eq!(
serde_json::to_string(&c).unwrap(),
r#"{"children":[{"type":"paragraph","data":{"id":"12345678","children":[{"type":"run","data":{"runProperty":{"sz":null,"szCs":null,"color":null,"highlight":null,"underline":null,"bold":null,"boldCs":null,"italic":null,"italicCs":null,"vanish":null,"spacing":null,"fonts":null},"children":[{"type":"text","data":{"preserveSpace":true,"text":"Hello"}}]}}],"property":{"runProperty":{"sz":null,"szCs":null,"color":null,"highlight":null,"underline":null,"bold":null,"boldCs":null,"italic":null,"italicCs":null,"vanish":null,"spacing":null,"fonts":null},"style":null,"numberingProperty":null,"alignment":null,"indent":null,"lineHeight":null},"hasNumbering":false}}],"property":{"width":null,"borders":null,"gridSpan":2,"verticalMerge":null,"verticalAlign":null,"textDirection":null,"shading":null},"hasNumbering":false}"#
r#"{"children":[{"type":"paragraph","data":{"id":"12345678","children":[{"type":"run","data":{"runProperty":{"sz":null,"szCs":null,"color":null,"highlight":null,"underline":null,"bold":null,"boldCs":null,"italic":null,"italicCs":null,"vanish":null,"spacing":null,"fonts":null,"textBorder":null},"children":[{"type":"text","data":{"preserveSpace":true,"text":"Hello"}}]}}],"property":{"runProperty":{"sz":null,"szCs":null,"color":null,"highlight":null,"underline":null,"bold":null,"boldCs":null,"italic":null,"italicCs":null,"vanish":null,"spacing":null,"fonts":null,"textBorder":null},"style":null,"numberingProperty":null,"alignment":null,"indent":null,"lineHeight":null},"hasNumbering":false}}],"property":{"width":null,"borders":null,"gridSpan":2,"verticalMerge":null,"verticalAlign":null,"textDirection":null,"shading":null},"hasNumbering":false}"#
);
}
}

View File

@ -0,0 +1,59 @@
use serde::{Deserialize, Serialize};
use crate::documents::BuildXML;
use crate::types::*;
use crate::xml_builder::*;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TextBorder {
pub border_type: BorderType,
pub size: usize,
pub color: String,
pub space: usize,
}
impl TextBorder {
pub fn new() -> Self {
TextBorder::default()
}
pub fn color(mut self, color: impl Into<String>) -> Self {
self.color = color.into();
self
}
pub fn size(mut self, size: usize) -> Self {
self.size = size;
self
}
pub fn space(mut self, space: usize) -> Self {
self.space = space;
self
}
pub fn border_type(mut self, border_type: BorderType) -> Self {
self.border_type = border_type;
self
}
}
impl Default for TextBorder {
fn default() -> Self {
TextBorder {
border_type: BorderType::Single,
size: 4,
space: 0,
color: "auto".to_owned(),
}
}
}
impl BuildXML for TextBorder {
fn build(&self) -> Vec<u8> {
let b = XMLBuilder::new();
b.text_border(self.border_type, self.size, self.space, &self.color)
.build()
}
}

View File

@ -45,6 +45,18 @@ impl ElementReader for RunProperty {
rp = rp.italic();
}
XMLElement::Vanish => rp = rp.vanish(),
XMLElement::TextBorder => {
if let Ok(attr) = read_border(&attributes) {
let mut border = TextBorder::new()
.border_type(attr.border_type)
.color(attr.color);
if let Some(size) = attr.size {
border = border.size(size as usize);
};
rp = rp.text_border(border);
continue;
}
}
_ => {}
}
}

View File

@ -19,6 +19,7 @@ pub enum XMLElement {
SizeCs,
Spacing,
Vanish,
TextBorder,
Italic,
ItalicCs,
Text,
@ -254,6 +255,7 @@ impl FromStr for XMLElement {
"gridCol" => Ok(XMLElement::GridCol),
"style" => Ok(XMLElement::Style),
"basedOn" => Ok(XMLElement::BasedOn),
"bdr" => Ok(XMLElement::TextBorder),
"next" => Ok(XMLElement::Next),
"vertAlign" => Ok(XMLElement::VertAlign),
"spacing" => Ok(XMLElement::Spacing),

View File

@ -1,7 +1,7 @@
//
// Please see p3813 <xsd:simpleType name="ST_Border">
//
use serde::Serialize;
use serde::{Deserialize, Serialize};
use std::fmt;
use wasm_bindgen::prelude::*;
@ -9,7 +9,7 @@ use super::errors;
use std::str::FromStr;
#[wasm_bindgen]
#[derive(Copy, Clone, Debug, PartialEq, Serialize)]
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum BorderType {
Nil,

View File

@ -210,6 +210,8 @@ impl XMLBuilder {
closed_border_el!(border_tl2br, "w:tl2br");
closed_border_el!(border_tr2bl, "w:tr2bl");
closed_border_el!(text_border, "w:bdr");
closed!(shd, "w:shd", "w:val", "w:color", "w:fill");
closed!(tab, "w:tab");

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,32 @@
export type BorderType =
| "nil"
| "none"
| "single"
| "thick"
| "double"
| "dotted"
| "dashed"
| "dotDash"
| "dotDotDash"
| "triple"
| "thinThickSmallGap"
| "thickThinSmallGap"
| "thinThickThinSmallGap"
| "thinThickMediumGap"
| "thickThinMediumGap"
| "thinThickThinMediumGap"
| "thinThickLargeGap"
| "thickThinLargeGap"
| "thinThickThinLargeGap"
| "wave"
| "doubleWave"
| "dashSmallGap"
| "dashDotStroked"
| "threeDEmboss"
| "threeDEngrave"
| "outset"
| "inset"
| "apples"
| "archedScallops"
| "babyPacifier"
| "babyRattle";

View File

@ -4,7 +4,7 @@ import { Delete } from "./delete";
import { DeleteText } from "./delete-text";
import { Table } from "./table";
import { TableCell, toTextDirectionWasmType } from "./table-cell";
import { BorderType } from "./table-cell-border";
import { BorderType } from "./border";
import { Run, RunFonts } from "./run";
import { Text } from "./text";
import { Tab } from "./tab";
@ -216,6 +216,11 @@ export class Docx {
run = run.spacing(r.property.spacing);
}
if (r.property.textBorder) {
const { borderType, color, space, size } = r.property.textBorder;
run = run.text_border(convertBorderType(borderType), size, space, color);
}
const fonts = this.buildRunFonts(r.property.fonts);
run = run.fonts(fonts);
@ -794,6 +799,7 @@ export const readDocx = (buf: Uint8Array) => {
export * from "./paragraph";
export * from "./insert";
export * from "./delete";
export * from "./border";
export * from "./table";
export * from "./table-cell";
export * from "./table-cell-border";

View File

@ -1,6 +1,13 @@
import { DrawingJSON } from "./drawing";
import { CommentRangeStartJSON, CommentRangeEndJSON } from "..";
export type TextBorderJSON = {
borderType: string;
size: number;
space: number;
color: string;
};
export type RunPropertyJSON = {
sz: number | null;
szCs: number | null;
@ -13,6 +20,7 @@ export type RunPropertyJSON = {
italicCs: boolean | null;
vanish: boolean | null;
spacing: number | null;
TextBorder: TextBorderJSON | null;
};
export type RunChildJSON =

View File

@ -2,9 +2,17 @@ import { Text } from "./text";
import { DeleteText } from "./delete-text";
import { Tab } from "./tab";
import { Break, BreakType } from "./break";
import { BorderType } from "./border";
export type RunChild = Text | DeleteText | Tab | Break;
export type TextBorder = {
borderType: BorderType;
color: string;
space: number;
size: number;
};
export type RunProperty = {
size?: number;
color?: string;
@ -15,6 +23,7 @@ export type RunProperty = {
vanish?: boolean;
fonts?: RunFonts;
spacing?: number;
textBorder?: TextBorder;
};
export class RunFonts {
@ -112,4 +121,17 @@ export class Run {
this.property = { ...this.property, spacing };
return this;
}
textBorder(type: BorderType, size: number, space: number, color: string) {
this.property = {
...this.property,
textBorder: {
borderType: type,
size,
space,
color,
},
};
return this;
}
}

View File

@ -1,35 +1,4 @@
export type BorderType =
| "nil"
| "none"
| "single"
| "thick"
| "double"
| "dotted"
| "dashed"
| "dotDash"
| "dotDotDash"
| "triple"
| "thinThickSmallGap"
| "thickThinSmallGap"
| "thinThickThinSmallGap"
| "thinThickMediumGap"
| "thickThinMediumGap"
| "thinThickThinMediumGap"
| "thinThickLargeGap"
| "thickThinLargeGap"
| "thinThickThinLargeGap"
| "wave"
| "doubleWave"
| "dashSmallGap"
| "dashDotStroked"
| "threeDEmboss"
| "threeDEngrave"
| "outset"
| "inset"
| "apples"
| "archedScallops"
| "babyPacifier"
| "babyRattle";
import { BorderType } from "./border";
export type TableCellBorderPosition =
| "left"

View File

@ -1,6 +1,6 @@
{
"name": "docx-wasm",
"version": "0.0.170",
"version": "0.0.171",
"main": "dist/node/index.js",
"browser": "dist/web/index.js",
"author": "bokuweb <bokuweb12@gmail.com>",

View File

@ -1,4 +1,5 @@
use super::*;
use docx_rs::{BorderType, TextBorder};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
@ -84,6 +85,22 @@ impl Run {
self.0.run_property = self.0.run_property.spacing(spacing);
self
}
pub fn text_border(
mut self,
border_type: BorderType,
size: usize,
space: usize,
color: &str,
) -> Run {
let border = TextBorder::new()
.border_type(border_type)
.size(size)
.space(space)
.color(color);
self.0.run_property = self.0.run_property.text_border(border);
self
}
}
impl Run {

File diff suppressed because it is too large Load Diff

View File

@ -208,4 +208,18 @@ describe("writer", () => {
}
}
});
test("should write text border", () => {
const p = new w.Paragraph()
.addRun(new w.Run().addText("Hello "))
.addRun(new w.Run().addText("World!").textBorder("single", 4, 0, "auto"));
const buffer = new w.Docx().addParagraph(p).build();
writeFileSync("../output/text_border.docx", buffer);
const z = new Zip(Buffer.from(buffer));
for (const e of z.getEntries()) {
if (e.entryName.match(/document.xml|numbering.xml/)) {
expect(z.readAsText(e)).toMatchSnapshot();
}
}
});
});