Support tcmar (#692)

* feat: Support tcMar writer

* feat: read tcMar

* fix: generate table cell border

* beta14

* beta15
main
bokuweb 2024-03-25 15:37:32 +09:00 committed by GitHub
parent e182f0e2d3
commit 2bbefbd6da
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 342 additions and 43 deletions

View File

@ -0,0 +1,109 @@
use serde::Serialize;
use crate::documents::BuildXML;
use crate::types::*;
use crate::xml_builder::*;
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CellMargin {
pub val: usize,
pub width_type: WidthType,
}
impl CellMargin {
pub fn new(val: usize, t: WidthType) -> Self {
Self { val, width_type: t }
}
}
impl Default for CellMargin {
fn default() -> CellMargin {
CellMargin {
val: 55,
width_type: WidthType::Dxa,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct CellMargins {
#[serde(skip_serializing_if = "Option::is_none")]
top: Option<CellMargin>,
#[serde(skip_serializing_if = "Option::is_none")]
left: Option<CellMargin>,
#[serde(skip_serializing_if = "Option::is_none")]
bottom: Option<CellMargin>,
#[serde(skip_serializing_if = "Option::is_none")]
right: Option<CellMargin>,
}
impl CellMargins {
pub fn new() -> CellMargins {
Default::default()
}
pub fn margin_top(mut self, v: usize, t: WidthType) -> Self {
self.top = Some(CellMargin::new(v, t));
self
}
pub fn margin_right(mut self, v: usize, t: WidthType) -> Self {
self.right = Some(CellMargin::new(v, t));
self
}
pub fn margin_left(mut self, v: usize, t: WidthType) -> Self {
self.left = Some(CellMargin::new(v, t));
self
}
pub fn margin_bottom(mut self, v: usize, t: WidthType) -> Self {
self.bottom = Some(CellMargin::new(v, t));
self
}
}
impl BuildXML for CellMargins {
fn build(&self) -> Vec<u8> {
let mut b = XMLBuilder::new().open_cell_margins();
if let Some(ref top) = self.top {
b = b.margin_top(top.val as i32, top.width_type);
}
if let Some(ref left) = self.left {
b = b.margin_left(left.val as i32, left.width_type);
}
if let Some(ref bottom) = self.bottom {
b = b.margin_bottom(bottom.val as i32, bottom.width_type);
}
if let Some(ref right) = self.right {
b = b.margin_right(right.val as i32, right.width_type);
}
b.close().build()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(test)]
use pretty_assertions::assert_eq;
use std::str;
#[test]
fn test_cell_margin() {
let b = CellMargins::new().margin_top(10, WidthType::Dxa).build();
assert_eq!(
str::from_utf8(&b).unwrap(),
r#"<w:tcMar>
<w:top w:w="10" w:type="dxa" />
</w:tcMar>"#
);
}
}

View File

@ -9,6 +9,7 @@ mod bookmark_end;
mod bookmark_start; mod bookmark_start;
mod br; mod br;
mod caps; mod caps;
mod cell_margins;
mod character_spacing; mod character_spacing;
mod color; mod color;
mod comment; mod comment;
@ -139,6 +140,7 @@ pub use bookmark_end::*;
pub use bookmark_start::*; pub use bookmark_start::*;
pub use br::*; pub use br::*;
pub use caps::*; pub use caps::*;
pub use cell_margins::*;
pub use character_spacing::*; pub use character_spacing::*;
pub use color::*; pub use color::*;
pub use comment::*; pub use comment::*;

View File

@ -17,6 +17,8 @@ use crate::xml_builder::*;
tr2bl diagonal border from top right corner to bottom left corner tr2bl diagonal border from top right corner to bottom left corner
*/ */
#[derive(Serialize, Debug, Clone, PartialEq)] #[derive(Serialize, Debug, Clone, PartialEq)]
#[cfg_attr(feature = "wasm", derive(ts_rs::TS))]
#[cfg_attr(feature = "wasm", ts(export))]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct TableCellBorder { pub struct TableCellBorder {
pub border_type: BorderType, pub border_type: BorderType,
@ -99,6 +101,8 @@ impl BuildXML for TableCellBorder {
} }
#[derive(Serialize, Debug, Clone, PartialEq)] #[derive(Serialize, Debug, Clone, PartialEq)]
#[cfg_attr(feature = "wasm", derive(ts_rs::TS))]
#[cfg_attr(feature = "wasm", ts(export))]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct TableCellBorders { pub struct TableCellBorders {
top: Option<TableCellBorder>, top: Option<TableCellBorder>,

View File

@ -3,28 +3,7 @@ use serde::Serialize;
use crate::documents::BuildXML; use crate::documents::BuildXML;
use crate::types::*; use crate::types::*;
use crate::xml_builder::*; use crate::xml_builder::*;
use crate::CellMargin;
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
struct CellMargin {
pub val: usize,
pub width_type: WidthType,
}
impl CellMargin {
pub fn new(val: usize, t: WidthType) -> Self {
Self { val, width_type: t }
}
}
impl Default for CellMargin {
fn default() -> CellMargin {
CellMargin {
val: 55,
width_type: WidthType::Dxa,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize)] #[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]

View File

@ -8,7 +8,7 @@ use crate::types::*;
use crate::xml_builder::*; use crate::xml_builder::*;
#[cfg_attr(feature = "wasm", wasm_bindgen)] #[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Serialize, Debug, Clone, PartialEq)] #[derive(Serialize, Debug, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct TableCellProperty { pub struct TableCellProperty {
width: Option<TableCellWidth>, width: Option<TableCellWidth>,
@ -18,6 +18,8 @@ pub struct TableCellProperty {
vertical_align: Option<VAlign>, vertical_align: Option<VAlign>,
text_direction: Option<TextDirection>, text_direction: Option<TextDirection>,
shading: Option<Shading>, shading: Option<Shading>,
#[serde(skip_serializing_if = "Option::is_none")]
margins: Option<CellMargins>,
} }
impl TableCellProperty { impl TableCellProperty {
@ -74,19 +76,50 @@ impl TableCellProperty {
self.borders = Some(self.borders.unwrap_or_default().clear_all()); self.borders = Some(self.borders.unwrap_or_default().clear_all());
self self
} }
}
impl Default for TableCellProperty { pub fn margins(mut self, margins: CellMargins) -> Self {
fn default() -> Self { self.margins = Some(margins);
TableCellProperty { self
width: None, }
borders: None,
grid_span: None, pub fn margin_top(mut self, v: usize, t: WidthType) -> Self {
vertical_merge: None, if let Some(margins) = self.margins {
vertical_align: None, self.margins = Some(margins.margin_top(v, t));
text_direction: None, } else {
shading: None, let margins = CellMargins::new();
self.margins = Some(margins.margin_top(v, t));
} }
self
}
pub fn margin_right(mut self, v: usize, t: WidthType) -> Self {
if let Some(margins) = self.margins {
self.margins = Some(margins.margin_right(v, t));
} else {
let margins = CellMargins::new();
self.margins = Some(margins.margin_right(v, t));
}
self
}
pub fn margin_bottom(mut self, v: usize, t: WidthType) -> Self {
if let Some(margins) = self.margins {
self.margins = Some(margins.margin_bottom(v, t));
} else {
let margins = CellMargins::new();
self.margins = Some(margins.margin_bottom(v, t));
}
self
}
pub fn margin_left(mut self, v: usize, t: WidthType) -> Self {
if let Some(margins) = self.margins {
self.margins = Some(margins.margin_left(v, t));
} else {
let margins = CellMargins::new();
self.margins = Some(margins.margin_left(v, t));
}
self
} }
} }
@ -101,6 +134,7 @@ impl BuildXML for TableCellProperty {
.add_optional_child(&self.vertical_align) .add_optional_child(&self.vertical_align)
.add_optional_child(&self.text_direction) .add_optional_child(&self.text_direction)
.add_optional_child(&self.shading) .add_optional_child(&self.shading)
.add_optional_child(&self.margins)
.close() .close()
.build() .build()
} }

View File

@ -0,0 +1,54 @@
use std::io::Read;
use std::str::FromStr;
use xml::attribute::OwnedAttribute;
use xml::reader::{EventReader, XmlEvent};
use super::*;
impl ElementReader for CellMargins {
fn read<R: Read>(r: &mut EventReader<R>, _: &[OwnedAttribute]) -> Result<Self, ReaderError> {
let mut margins = CellMargins::default();
loop {
let e = r.next();
match e {
Ok(XmlEvent::StartElement {
attributes, name, ..
}) => {
let e = XMLElement::from_str(&name.local_name).unwrap();
match e {
XMLElement::Top => {
if let Ok(width) = read_width(&attributes) {
margins = margins.margin_top(width.0 as usize, width.1)
}
}
XMLElement::Right => {
if let Ok(width) = read_width(&attributes) {
margins = margins.margin_right(width.0 as usize, width.1)
}
}
XMLElement::Bottom => {
if let Ok(width) = read_width(&attributes) {
margins = margins.margin_bottom(width.0 as usize, width.1)
}
}
XMLElement::Left => {
if let Ok(width) = read_width(&attributes) {
margins = margins.margin_left(width.0 as usize, width.1)
}
}
_ => {}
}
}
Ok(XmlEvent::EndElement { name, .. }) => {
let e = XMLElement::from_str(&name.local_name).unwrap();
if e == XMLElement::CellMargins {
return Ok(margins);
}
}
Err(_) => return Err(ReaderError::XMLReadError),
_ => {}
}
}
}
}

View File

@ -3,6 +3,7 @@ mod a_graphic_data;
mod attributes; mod attributes;
mod bookmark_end; mod bookmark_end;
mod bookmark_start; mod bookmark_start;
mod cell_margins;
mod comment; mod comment;
mod comment_extended; mod comment_extended;
mod comments; mod comments;

View File

@ -67,6 +67,11 @@ impl ElementReader for TableCellProperty {
let borders = TableCellBorders::read(r, &attributes)?; let borders = TableCellBorders::read(r, &attributes)?;
property = property.set_borders(borders); property = property.set_borders(borders);
} }
XMLElement::CellMargins => {
if let Ok(margins) = CellMargins::read(r, &attributes) {
property = property.margins(margins);
}
}
_ => {} _ => {}
} }
} }

View File

@ -99,6 +99,7 @@ pub enum XMLElement {
TablePropertyChange, TablePropertyChange,
TableRowPropertyChange, TableRowPropertyChange,
TableCellPropertyChange, TableCellPropertyChange,
CellMargins,
Top, Top,
Right, Right,
End, End,
@ -340,6 +341,7 @@ impl FromStr for XMLElement {
"tblPrChange" => Ok(XMLElement::TablePropertyChange), "tblPrChange" => Ok(XMLElement::TablePropertyChange),
"trPrChange" => Ok(XMLElement::TableRowPropertyChange), "trPrChange" => Ok(XMLElement::TableRowPropertyChange),
"tcPrChange" => Ok(XMLElement::TableCellPropertyChange), "tcPrChange" => Ok(XMLElement::TableCellPropertyChange),
"tcMar" => Ok(XMLElement::CellMargins),
"tblGridChange" => Ok(XMLElement::TableGridChange), "tblGridChange" => Ok(XMLElement::TableGridChange),
"gridCol" => Ok(XMLElement::GridCol), "gridCol" => Ok(XMLElement::GridCol),
"style" => Ok(XMLElement::Style), "style" => Ok(XMLElement::Style),

View File

@ -14,7 +14,7 @@ pub enum TableBorderPosition {
InsideV, InsideV,
} }
#[cfg_attr(feature = "wasm", wasm_bindgen)] #[cfg_attr(feature = "wasm", wasm_bindgen, derive(ts_rs::TS), ts(export))]
#[derive(Debug, Clone, PartialEq, Serialize)] #[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub enum TableCellBorderPosition { pub enum TableCellBorderPosition {

View File

@ -9,7 +9,7 @@ use wasm_bindgen::prelude::*;
use super::errors; use super::errors;
use std::str::FromStr; use std::str::FromStr;
#[cfg_attr(feature = "wasm", wasm_bindgen)] #[cfg_attr(feature = "wasm", wasm_bindgen, derive(ts_rs::TS), ts(export))]
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub enum BorderType { pub enum BorderType {

View File

@ -319,6 +319,7 @@ impl XMLBuilder {
open!(open_table_cell_borders, "w:tcBorders"); open!(open_table_cell_borders, "w:tcBorders");
open!(open_table_borders, "w:tblBorders"); open!(open_table_borders, "w:tblBorders");
open!(open_table_cell_margins, "w:tblCellMar"); open!(open_table_cell_margins, "w:tblCellMar");
open!(open_cell_margins, "w:tcMar");
closed!(table_layout, "w:tblLayout", "w:type"); closed!(table_layout, "w:tblLayout", "w:type");
closed_with_str!(table_style, "w:tblStyle"); closed_with_str!(table_style, "w:tblStyle");

View File

@ -1,3 +1,2 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
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"; 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

@ -0,0 +1,4 @@
import type { BorderType } from "./BorderType";
import type { TableCellBorderPosition } from "./TableCellBorderPosition";
export interface TableCellBorder { borderType: BorderType, size: number, color: string, position: TableCellBorderPosition, space: number, }

View File

@ -0,0 +1,2 @@
export type TableCellBorderPosition = "left" | "right" | "top" | "bottom" | "insideH" | "insideV" | "tl2Br" | "tr2Bl";

View File

@ -0,0 +1,3 @@
import type { TableCellBorder } from "./TableCellBorder";
export interface TableCellBorders { top: TableCellBorder | null, left: TableCellBorder | null, bottom: TableCellBorder | null, right: TableCellBorder | null, insideH: TableCellBorder | null, insideV: TableCellBorder | null, tr2Bl: TableCellBorder | null, tl2Br: TableCellBorder | null, }

View File

@ -4,9 +4,13 @@ import { HeightRule } from "../table-row";
import { TextDirectionType } from "../table-cell"; import { TextDirectionType } from "../table-cell";
import { ShadingJSON } from "./shading"; import { ShadingJSON } from "./shading";
import { TableLayoutType } from "../table"; import { TableLayoutType } from "../table";
import { DeleteJSONData, InsertJSONData } from ".."; import { DeleteJSONData, InsertJSONData, TableCellBordersJSON } from "..";
import { StructuredTagJSON } from "./structured-data-tag"; import { StructuredTagJSON } from "./structured-data-tag";
export { TableCellBorder as TableCellBorderJSON } from "./bindings/TableCellBorder";
export { TableCellBorders as TableCellBordersJSON } from "./bindings/TableCellBorders";
export type TableCellChildJSON = ParagraphJSON | TableJSON | StructuredTagJSON; export type TableCellChildJSON = ParagraphJSON | TableJSON | StructuredTagJSON;
export type WidthType = "dxa" | "auto" | "pct" | "nil"; export type WidthType = "dxa" | "auto" | "pct" | "nil";
@ -20,12 +24,13 @@ export type TableCellPropertyJSON = {
width: number; width: number;
widthType: WidthType; widthType: WidthType;
} | null; } | null;
borders: any | null; borders: TableCellBordersJSON | null;
gridSpan: number | null; gridSpan: number | null;
verticalMerge: "restart" | "continue" | null; verticalMerge: "restart" | "continue" | null;
verticalAlign: "top" | "center" | "bottom" | null; verticalAlign: "top" | "center" | "bottom" | null;
textDirection: TextDirectionType | null; textDirection: TextDirectionType | null;
shading: ShadingJSON | null; shading: ShadingJSON | null;
margins?: CellMarginsJSON;
}; };
export type TableRowPropertyJSON = { export type TableRowPropertyJSON = {
@ -64,6 +69,13 @@ export type TableCellMarginsJSON = {
right: TableCellMarginJSON; right: TableCellMarginJSON;
}; };
export type CellMarginsJSON = {
top: TableCellMarginJSON;
left: TableCellMarginJSON;
bottom: TableCellMarginJSON;
right: TableCellMarginJSON;
};
export type TablePropertyJSON = { export type TablePropertyJSON = {
width: { width: {
width: number; width: number;

View File

@ -1,5 +1,5 @@
import { Paragraph } from "./paragraph"; import { Paragraph } from "./paragraph";
import { Table } from "./table"; import { Table, convertWidthType } from "./table";
import { Shading } from "./shading"; import { Shading } from "./shading";
import { TableCellBorders, PositionKeys } from "./table-cell-borders"; import { TableCellBorders, PositionKeys } from "./table-cell-borders";
import { TableCellBorderPosition, TableCellBorder } from "./table-cell-border"; import { TableCellBorderPosition, TableCellBorder } from "./table-cell-border";
@ -7,6 +7,7 @@ import * as wasm from "./pkg";
import { convertBorderType } from "./run"; import { convertBorderType } from "./run";
import { TableOfContents } from "./table-of-contents"; import { TableOfContents } from "./table-of-contents";
import { build } from "./builder"; import { build } from "./builder";
import { WidthType } from ".";
export type VMergeType = "restart" | "continue"; export type VMergeType = "restart" | "continue";
@ -61,6 +62,12 @@ export type CellProperty = {
width?: number; width?: number;
textDirection?: TextDirectionType; textDirection?: TextDirectionType;
shading?: Shading; shading?: Shading;
margins?: {
top?: { val: number; type: WidthType };
left?: { val: number; type: WidthType };
bottom?: { val: number; type: WidthType };
right?: { val: number; type: WidthType };
};
}; };
export class TableCell { export class TableCell {
@ -137,6 +144,38 @@ export class TableCell {
return this; return this;
} }
marginTop(v: number, t: WidthType) {
this.property.margins = {
...this.property.margins,
top: { val: v, type: t },
};
return this;
}
marginLeft(v: number, t: WidthType) {
this.property.margins = {
...this.property.margins,
left: { val: v, type: t },
};
return this;
}
marginRight(v: number, t: WidthType) {
this.property.margins = {
...this.property.margins,
right: { val: v, type: t },
};
return this;
}
marginBottom(v: number, t: WidthType) {
this.property.margins = {
...this.property.margins,
bottom: { val: v, type: t },
};
return this;
}
buildCellBorders(cell: wasm.TableCell): wasm.TableCell { buildCellBorders(cell: wasm.TableCell): wasm.TableCell {
if (this.property.borders.top) { if (this.property.borders.top) {
const border = wasm const border = wasm
@ -224,6 +263,34 @@ export class TableCell {
cell = cell.set_border(border); cell = cell.set_border(border);
} }
if (this.property.margins?.top) {
cell = cell.margin_top(
this.property.margins?.top.val,
convertWidthType(this.property.margins?.top.type)
);
}
if (this.property.margins?.right) {
cell = cell.margin_right(
this.property.margins?.right.val,
convertWidthType(this.property.margins?.right.type)
);
}
if (this.property.margins?.bottom) {
cell = cell.margin_bottom(
this.property.margins?.bottom.val,
convertWidthType(this.property.margins?.bottom.type)
);
}
if (this.property.margins?.left) {
cell = cell.margin_left(
this.property.margins?.left.val,
convertWidthType(this.property.margins?.left.type)
);
}
return cell; return cell;
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "docx-wasm", "name": "docx-wasm",
"version": "0.4.12-beta9", "version": "0.4.12-beta15",
"main": "dist/node/index.js", "main": "dist/node/index.js",
"browser": "dist/web/index.js", "browser": "dist/web/index.js",
"author": "bokuweb <bokuweb12@gmail.com>", "author": "bokuweb <bokuweb12@gmail.com>",
@ -52,3 +52,4 @@
"types": "dist/web/index.d.ts", "types": "dist/web/index.d.ts",
"dependencies": {} "dependencies": {}
} }

View File

@ -1,7 +1,7 @@
use std::str::FromStr; use std::str::FromStr;
use super::*; use super::*;
use docx_rs::Shading; use docx_rs::{Shading, WidthType};
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
#[wasm_bindgen] #[wasm_bindgen]
@ -92,4 +92,24 @@ impl TableCell {
self.0.property = self.0.property.clear_all_border(); self.0.property = self.0.property.clear_all_border();
self self
} }
pub fn margin_top(mut self, v: usize, t: WidthType) -> Self {
self.0.property = self.0.property.margin_top(v, t);
self
}
pub fn margin_right(mut self, v: usize, t: WidthType) -> Self {
self.0.property = self.0.property.margin_right(v, t);
self
}
pub fn margin_bottom(mut self, v: usize, t: WidthType) -> Self {
self.0.property = self.0.property.margin_bottom(v, t);
self
}
pub fn margin_left(mut self, v: usize, t: WidthType) -> Self {
self.0.property = self.0.property.margin_left(v, t);
self
}
} }