diff --git a/.gitignore b/.gitignore index a4de448..1c7e3e2 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,9 @@ node_modules pkg *.docx# test.docx -output/*.docx \ No newline at end of file +output/*.docx +vrt/screenshot/actual +vrt/screenshot/diff +vrt/report.html +reg.json +docx-core/tests/output/*.docx \ No newline at end of file diff --git a/docx-core/src/documents/document.rs b/docx-core/src/documents/document.rs index a1a11af..3695b98 100644 --- a/docx-core/src/documents/document.rs +++ b/docx-core/src/documents/document.rs @@ -57,7 +57,7 @@ mod tests { str::from_utf8(&b).unwrap(), r#" - Hello + Hello "# ); } diff --git a/docx-core/src/documents/elements/paragraph.rs b/docx-core/src/documents/elements/paragraph.rs index 8e43fc6..5fc7d91 100644 --- a/docx-core/src/documents/elements/paragraph.rs +++ b/docx-core/src/documents/elements/paragraph.rs @@ -73,7 +73,7 @@ mod tests { let b = Paragraph::new().add_run(Run::new("Hello")).build(); assert_eq!( str::from_utf8(&b).unwrap(), - r#"Hello"# + r#"Hello"# ); } @@ -82,7 +82,7 @@ mod tests { let b = Paragraph::new().add_run(Run::new("Hello")).size(60).build(); assert_eq!( str::from_utf8(&b).unwrap(), - r#"Hello"# + r#"Hello"# ); } } diff --git a/docx-core/src/documents/elements/style.rs b/docx-core/src/documents/elements/style.rs index 0a91b4c..9ffd107 100644 --- a/docx-core/src/documents/elements/style.rs +++ b/docx-core/src/documents/elements/style.rs @@ -75,7 +75,7 @@ mod tests { let b = c.build(); assert_eq!( str::from_utf8(&b).unwrap(), - r#""# + r#""# ); } } diff --git a/docx-core/src/documents/styles.rs b/docx-core/src/documents/styles.rs index dd79f32..f1e8cfe 100644 --- a/docx-core/src/documents/styles.rs +++ b/docx-core/src/documents/styles.rs @@ -49,12 +49,12 @@ mod tests { use std::str; #[test] - fn test_build() { + fn test_style() { let c = Styles::new().add_style(Style::new("Title", "TitleName", StyleType::Paragraph)); let b = c.build(); assert_eq!( str::from_utf8(&b).unwrap(), - r#""# + r#""# ); } } diff --git a/docx-core/src/types/border_type.rs b/docx-core/src/types/border_type.rs new file mode 100644 index 0000000..40d9265 --- /dev/null +++ b/docx-core/src/types/border_type.rs @@ -0,0 +1,199 @@ +// +// Please see p3813 +// + +/* +"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" +"balloons3Colors" +"balloonsHotAir" +"basicBlackDashes" +"basicBlackDots" +"basicBlackSquares" +"basicThinLines" +"basicWhiteDashes" +"basicWhiteDots" +"basicWhiteSquares" +"basicWideInline" +"basicWideMidline" +"basicWideOutline" +"bats" +"birds" +"birdsFlight" +"cabins" +"cakeSlice" +"candyCorn" +"celticKnotwork" +"certificateBanner" +"chainLink" +"champagneBottle" +"checkedBarBlack" +"checkedBarColor" +"checkered" +"christmasTree" +"circlesLines" +"circlesRectangles" +"classicalWave" +"clocks" +"compass" +"confetti" +"confettiGrays" +"confettiOutline" +"confettiStreamers" +"confettiWhite" +"cornerTriangles" +"couponCutoutDashes" +"couponCutoutDots" +"crazyMaze" +"creaturesButterfly" +"creaturesFish" +"creaturesInsects" +"creaturesLadyBug" +"crossStitch" +"cup" +"decoArch" +"decoArchColor" +"decoBlocks" +"diamondsGray" +"doubleD" +"doubleDiamonds" +"earth1" +"earth2" +"earth3" +"eclipsingSquares1" +"eclipsingSquares2" +"eggsBlack" +"fans" +"film" +"firecrackers" +"flowersBlockPrint" +"flowersDaisies" +"flowersModern1" +"flowersModern2" +"flowersPansy" +"flowersRedRose" +"flowersRoses" +"flowersTeacup" +"flowersTiny" +"gems" +"gingerbreadMan" +"gradient" +"handmade1" +"handmade2" +"heartBalloon" +"heartGray" +"hearts" +"heebieJeebies" +"holly" +"houseFunky" +"hypnotic" +"iceCreamCones" +"lightBulb" +"lightning1" +"lightning2" +"mapPins" +"mapleLeaf" +"mapleMuffins" +"marquee" +"marqueeToothed" +"moons" +"mosaic" +"musicNotes" +"northwest" +"ovals" +"packages" +"palmsBlack" +"palmsColor" +"paperClips" +"papyrus" +"partyFavor" +"partyGlass" +"pencils" +"people" +"peopleWaving" +"peopleHats" +"poinsettias" +"postageStamp" +"pumpkin1" +"pushPinNote2" +"pushPinNote1" +"pyramids" +"pyramidsAbove" +"quadrants" +"rings" +"safari" +"sawtooth" +"sawtoothGray" +"scaredCat" +"seattle" +"shadowedSquares" +"sharksTeeth" +"shorebirdTracks" +"skyrocket" +"snowflakeFancy" +"snowflakes" +"sombrero" +"southwest" +"stars" +"starsTop" +"stars3d" +"starsBlack" +"starsShadowed" +"sun" +"swirligig" +"tornPaper" +"tornPaperBlack" +"trees" +"triangleParty" +"triangles" +"triangle1" +"triangle2" +"triangleCircle1" +"triangleCircle2" +"shapes1" +"shapes2" +"twistedLines1" +"twistedLines2" +"vine" +"waveline" +"weavingAngles" +"weavingBraid" +"weavingRibbon" +"weavingStrips" +"whiteFlowers" +"woodwork" +"xIllusions" +"zanyTriangles" +"zigZag" +"zigZagStitch" +"custom" +*/ diff --git a/docx-core/src/types/mod.rs b/docx-core/src/types/mod.rs index 243f6c6..5798544 100644 --- a/docx-core/src/types/mod.rs +++ b/docx-core/src/types/mod.rs @@ -1,7 +1,9 @@ pub mod alignment_type; pub mod special_indent_type; pub mod style_type; +pub mod width_type; pub use alignment_type::*; pub use special_indent_type::*; pub use style_type::*; +pub use width_type::*; diff --git a/docx-core/src/types/width_type.rs b/docx-core/src/types/width_type.rs new file mode 100644 index 0000000..f51bcfc --- /dev/null +++ b/docx-core/src/types/width_type.rs @@ -0,0 +1,16 @@ +use std::fmt; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +#[derive(Copy, Clone, Debug)] +pub enum WidthType { + DXA, +} + +impl fmt::Display for WidthType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + WidthType::DXA => write!(f, "dxa"), + } + } +} diff --git a/docx-core/src/xml_builder/elements.rs b/docx-core/src/xml_builder/elements.rs index 34bb5e9..db10969 100644 --- a/docx-core/src/xml_builder/elements.rs +++ b/docx-core/src/xml_builder/elements.rs @@ -87,6 +87,29 @@ impl XMLBuilder { }; self.close() } + + // + // Table elements + // + opened_el!(open_table, "w:tbl"); + opened_el!(open_table_property, "w:tblPr"); + opened_el!(open_table_grid, "w:tblGrid"); + opened_el!(open_table_row, "w:tr"); + opened_el!(open_table_row_property, "w:trPr"); + opened_el!(open_table_cell, "w:tc"); + opened_el!(open_table_cell_property, "w:tcPr"); + opened_el!(open_table_borders, "w:tblBorders"); + opened_el!(open_table_cell_margins, "w:tblCellMar"); + + closed_w_with_type_el!(table_width, "w:tblW"); + closed_w_with_type_el!(table_indent, "w:tblInd"); + closed_w_with_type_el!(grid_column, "w:gridCol"); + closed_w_with_type_el!(table_cell_width, "w:tcW"); + + // TODO: + // w:shd + // w:top/left/bottom/right + // w:insideH/insideV } #[cfg(test)] diff --git a/docx-core/src/xml_builder/macros.rs b/docx-core/src/xml_builder/macros.rs index ad3981d..daf211f 100644 --- a/docx-core/src/xml_builder/macros.rs +++ b/docx-core/src/xml_builder/macros.rs @@ -152,3 +152,33 @@ macro_rules! only_usize_val_el { } }; } + +macro_rules! closed_w_with_type_el { + ($name: ident, $el_name: expr) => { + pub(crate) fn $name(mut self, w: usize, t: WidthType) -> Self { + self.writer + .write( + XmlEvent::start_element($el_name) + .attr("w:w", &format!("{}", w)) + .attr("w:type", &t.to_string()), + ) + .expect(EXPECT_MESSAGE); + self.close() + } + }; +} + +macro_rules! closed_border_el { + ($name: ident, $el_name: expr) => { + pub(crate) fn $name(mut self, size: usize, space: usize, color: &str) -> Self { + self.writer + .write( + XmlEvent::start_element($el_name) + .attr("w:w", &format!("{}", w)) + .attr("w:type", &t.to_string()), + ) + .expect(EXPECT_MESSAGE); + self.close() + } + }; +} diff --git a/docx-core/tests/lib.rs b/docx-core/tests/lib.rs new file mode 100644 index 0000000..f705007 --- /dev/null +++ b/docx-core/tests/lib.rs @@ -0,0 +1,60 @@ +extern crate docx_core; + +use docx_core::*; + +pub const DUMMY: &str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; + +#[test] +pub fn indent() -> Result<(), DocxError> { + let path = std::path::Path::new("./tests/output/indent.docx"); + let file = std::fs::File::create(&path).unwrap(); + Docx::new() + .add_paragraph(Paragraph::new().add_run(Run::new(DUMMY)).indent(840, None)) + .add_paragraph(Paragraph::new()) + .add_paragraph( + Paragraph::new() + .add_run(Run::new(DUMMY)) + .indent(840, Some(SpecialIndentType::FirstLine(720))), + ) + .add_paragraph(Paragraph::new()) + .add_paragraph( + Paragraph::new() + .add_run(Run::new(DUMMY)) + .indent(1560, Some(SpecialIndentType::Hanging(720))), + ) + .build() + .pack(file)?; + Ok(()) +} + +#[test] +pub fn size() -> Result<(), DocxError> { + let path = std::path::Path::new("./tests/output/size.docx"); + let file = std::fs::File::create(&path).unwrap(); + Docx::new() + .add_paragraph(Paragraph::new().add_run(Run::new("Hello")).size(60)) + .add_paragraph( + Paragraph::new() + .add_run(Run::new(" Wor").size(50)) + .add_run(Run::new("ld")), + ) + .build() + .pack(file)?; + Ok(()) +} + +#[test] +pub fn alignment() -> Result<(), DocxError> { + let path = std::path::Path::new("./tests/output/alignment.docx"); + let file = std::fs::File::create(&path).unwrap(); + Docx::new() + .add_paragraph(Paragraph::new().add_run(Run::new("Hello"))) + .add_paragraph( + Paragraph::new() + .add_run(Run::new(" World")) + .align(AlignmentType::Right), + ) + .build() + .pack(file)?; + Ok(()) +} diff --git a/docx-core/tests/output/.keep b/docx-core/tests/output/.keep new file mode 100644 index 0000000..e69de29 diff --git a/makefile b/makefile new file mode 100644 index 0000000..b48caa3 --- /dev/null +++ b/makefile @@ -0,0 +1,8 @@ +test: + cargo test + +vrt: + node vrt/index.js && reg-cli vrt/screenshot/actual vrt/screenshot/expected vrt/screenshot/diff -R vrt/report.html + +vrt-update: + node vrt/index.js && reg-cli vrt/screenshot/actual vrt/screenshot/expected vrt/screenshot/diff -R vrt/report.html -U \ No newline at end of file diff --git a/vrt/index.js b/vrt/index.js new file mode 100644 index 0000000..1d4a562 --- /dev/null +++ b/vrt/index.js @@ -0,0 +1,13 @@ +const glob = require("glob"); +const path = require("path"); +const createPDF = require("./pdf"); + +glob( + path.join(__dirname, "..", "./docx-core/tests/output/**/*.docx"), + {}, + async (err, files) => { + for await (file of files) { + await createPDF(file, path.join(__dirname, "./screenshot/actual")); + } + } +); diff --git a/vrt/package.json b/vrt/package.json new file mode 100644 index 0000000..7120501 --- /dev/null +++ b/vrt/package.json @@ -0,0 +1,14 @@ +{ + "name": "docx-rs", + "version": "1.0.0", + "main": "index.js", + "repository": "https://github.com/bokuweb/docx-rs.git", + "author": "bokuweb ", + "license": "MIT", + "devDependencies": { + "libreoffice-convert": "^1.0.3" + }, + "dependencies": { + "glob": "^7.1.6" + } +} diff --git a/vrt/pdf.js b/vrt/pdf.js new file mode 100644 index 0000000..ba5e597 --- /dev/null +++ b/vrt/pdf.js @@ -0,0 +1,25 @@ +const libre = require("libreoffice-convert"); +const path = require("path"); +const fs = require("fs"); + +const extend = "png"; + +module.exports = (docxPath, outputDir) => + new Promise((resolve, reject) => { + const filename = path.basename(docxPath, ".docx"); + const docxFile = fs.readFileSync(docxPath); + libre.convert(docxFile, extend, undefined, async (err, done) => { + if (err) { + reject(err); + } + try { + fs.mkdirSync(outputDir, { recursive: true }); + } catch (e) { + if (e.code !== "EEXIST") { + reject(e); + } + } + fs.writeFileSync(path.join(outputDir, `${filename}.${extend}`), done); + resolve(); + }); + }); diff --git a/vrt/screenshot/expected/alignment.png b/vrt/screenshot/expected/alignment.png new file mode 100644 index 0000000..3022f9b Binary files /dev/null and b/vrt/screenshot/expected/alignment.png differ diff --git a/vrt/screenshot/expected/indent.png b/vrt/screenshot/expected/indent.png new file mode 100644 index 0000000..ca69f6b Binary files /dev/null and b/vrt/screenshot/expected/indent.png differ diff --git a/vrt/screenshot/expected/size.png b/vrt/screenshot/expected/size.png new file mode 100644 index 0000000..e49024c Binary files /dev/null and b/vrt/screenshot/expected/size.png differ diff --git a/vrt/yarn.lock b/vrt/yarn.lock new file mode 100644 index 0000000..25d6e64 --- /dev/null +++ b/vrt/yarn.lock @@ -0,0 +1,109 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +async@^2.6.2: + version "2.6.3" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" + integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== + dependencies: + lodash "^4.17.14" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +glob@^7.1.3, glob@^7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +libreoffice-convert@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/libreoffice-convert/-/libreoffice-convert-1.0.3.tgz#ddcee6da4f271b6d161d9dfd8bf17db534e099a2" + integrity sha512-dr2cM5+pV0OGLxWYVEUKFdEBMe9cyaIiQ/OfnsI1aVYVSDDyaHA6dURRGzCSu0pAtIG3jUfvxqMSv8wWx6ttqQ== + dependencies: + async "^2.6.2" + temp "^0.9.0" + +lodash@^4.17.14: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +rimraf@~2.6.2: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +temp@^0.9.0: + version "0.9.1" + resolved "https://registry.yarnpkg.com/temp/-/temp-0.9.1.tgz#2d666114fafa26966cd4065996d7ceedd4dd4697" + integrity sha512-WMuOgiua1xb5R56lE0eH6ivpVmg/lq2OHm4+LtT/xtEtPQ+sz6N3bBM6WZ5FvO1lO4IKIOb43qnhoc4qxP5OeA== + dependencies: + rimraf "~2.6.2" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=