diff --git a/src/file/index.ts b/src/file/index.ts
index 24317b8598..d0c4b62282 100644
--- a/src/file/index.ts
+++ b/src/file/index.ts
@@ -17,3 +17,4 @@ export * from "./footnotes";
export * from "./track-revision";
export * from "./shared";
export * from "./border";
+export * from "./values";
diff --git a/src/file/paragraph/run/formatting.spec.ts b/src/file/paragraph/run/formatting.spec.ts
index 43a1de1fd0..cf590d8cf6 100644
--- a/src/file/paragraph/run/formatting.spec.ts
+++ b/src/file/paragraph/run/formatting.spec.ts
@@ -2,18 +2,35 @@ import { expect } from "chai";
import { Formatter } from "export/formatter";
-import { Bold } from "./formatting";
+import { CharacterSpacing, Color } from "./formatting";
-describe("Bold", () => {
+describe("CharacterSpacing", () => {
describe("#constructor()", () => {
it("should create", () => {
- const currentBold = new Bold();
+ const element = new CharacterSpacing(32);
- const tree = new Formatter().format(currentBold);
+ const tree = new Formatter().format(element);
expect(tree).to.deep.equal({
- "w:b": {
+ "w:spacing": {
_attr: {
- "w:val": true,
+ "w:val": 32,
+ },
+ },
+ });
+ });
+ });
+});
+
+describe("Color", () => {
+ describe("#constructor()", () => {
+ it("should create", () => {
+ const element = new Color("#FFFFFF");
+
+ const tree = new Formatter().format(element);
+ expect(tree).to.deep.equal({
+ "w:color": {
+ _attr: {
+ "w:val": "FFFFFF",
},
},
});
diff --git a/src/file/paragraph/run/formatting.ts b/src/file/paragraph/run/formatting.ts
index 3f560be6a1..5f735fdfbb 100644
--- a/src/file/paragraph/run/formatting.ts
+++ b/src/file/paragraph/run/formatting.ts
@@ -1,170 +1,55 @@
+import { hexColorValue, signedTwipsMeasureValue } from "file/values";
import { Attributes, XmlComponent } from "file/xml-components";
-export class Bold extends XmlComponent {
- constructor() {
- super("w:b");
- this.root.push(
- new Attributes({
- val: true,
- }),
- );
- }
-}
-
-export class BoldComplexScript extends XmlComponent {
- constructor() {
- super("w:bCs");
- this.root.push(
- new Attributes({
- val: true,
- }),
- );
- }
-}
-
export class CharacterSpacing extends XmlComponent {
- constructor(value: number) {
+ constructor(value: number | string) {
super("w:spacing");
this.root.push(
new Attributes({
- val: value,
- }),
- );
- }
-}
-
-export class Italics extends XmlComponent {
- constructor() {
- super("w:i");
- this.root.push(
- new Attributes({
- val: true,
- }),
- );
- }
-}
-
-export class ItalicsComplexScript extends XmlComponent {
- constructor() {
- super("w:iCs");
- this.root.push(
- new Attributes({
- val: true,
- }),
- );
- }
-}
-
-export class Caps extends XmlComponent {
- constructor() {
- super("w:caps");
- this.root.push(
- new Attributes({
- val: true,
+ val: signedTwipsMeasureValue(value),
}),
);
}
}
+//
+//
+//
+//
+//
+//
export class Color extends XmlComponent {
constructor(color: string) {
super("w:color");
this.root.push(
new Attributes({
- val: color,
- }),
- );
- }
-}
-
-export class DoubleStrike extends XmlComponent {
- constructor() {
- super("w:dstrike");
- this.root.push(
- new Attributes({
- val: true,
- }),
- );
- }
-}
-
-export class Emboss extends XmlComponent {
- constructor() {
- super("w:emboss");
- this.root.push(
- new Attributes({
- val: true,
- }),
- );
- }
-}
-
-export class Imprint extends XmlComponent {
- constructor() {
- super("w:imprint");
- this.root.push(
- new Attributes({
- val: true,
- }),
- );
- }
-}
-
-export class SmallCaps extends XmlComponent {
- constructor() {
- super("w:smallCaps");
- this.root.push(
- new Attributes({
- val: true,
- }),
- );
- }
-}
-
-export class Strike extends XmlComponent {
- constructor() {
- super("w:strike");
- this.root.push(
- new Attributes({
- val: true,
- }),
- );
- }
-}
-
-export class Size extends XmlComponent {
- constructor(size: number) {
- super("w:sz");
- this.root.push(
- new Attributes({
- val: size,
- }),
- );
- }
-}
-
-export class SizeComplexScript extends XmlComponent {
- constructor(size: number) {
- super("w:szCs");
- this.root.push(
- new Attributes({
- val: size,
- }),
- );
- }
-}
-
-export class RightToLeft extends XmlComponent {
- constructor() {
- super("w:rtl");
- this.root.push(
- new Attributes({
- val: true,
+ val: hexColorValue(color),
}),
);
}
}
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
export class Highlight extends XmlComponent {
constructor(color: string) {
super("w:highlight");
diff --git a/src/file/paragraph/run/properties.ts b/src/file/paragraph/run/properties.ts
index 73f5cb531e..277c540619 100644
--- a/src/file/paragraph/run/properties.ts
+++ b/src/file/paragraph/run/properties.ts
@@ -1,25 +1,7 @@
import { IShadingAttributesProperties, Shading } from "file/shading";
-import { IgnoreIfEmptyXmlComponent, XmlComponent } from "file/xml-components";
+import { HpsMeasureElement, IgnoreIfEmptyXmlComponent, OnOffElement, XmlComponent } from "file/xml-components";
import { EmphasisMark, EmphasisMarkType } from "./emphasis-mark";
-import {
- Bold,
- BoldComplexScript,
- Caps,
- CharacterSpacing,
- Color,
- DoubleStrike,
- Emboss,
- Highlight,
- HighlightComplexScript,
- Imprint,
- Italics,
- ItalicsComplexScript,
- RightToLeft,
- Size,
- SizeComplexScript,
- SmallCaps,
- Strike,
-} from "./formatting";
+import { CharacterSpacing, Color, Highlight, HighlightComplexScript } from "./formatting";
import { IFontAttributesProperties, RunFonts } from "./run-fonts";
import { SubScript, SuperScript } from "./script";
import { Style } from "./style";
@@ -43,8 +25,8 @@ export interface IRunStylePropertiesOptions {
readonly type?: EmphasisMarkType;
};
readonly color?: string;
- readonly size?: number;
- readonly sizeComplexScript?: boolean | number;
+ readonly size?: number | string;
+ readonly sizeComplexScript?: boolean | number | string;
readonly rightToLeft?: boolean;
readonly smallCaps?: boolean;
readonly allCaps?: boolean;
@@ -65,6 +47,49 @@ export interface IRunPropertiesOptions extends IRunStylePropertiesOptions {
readonly style?: string;
}
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
export class RunProperties extends IgnoreIfEmptyXmlComponent {
constructor(options?: IRunPropertiesOptions) {
super("w:rPr");
@@ -74,17 +99,17 @@ export class RunProperties extends IgnoreIfEmptyXmlComponent {
}
if (options.bold) {
- this.push(new Bold());
+ this.push(new OnOffElement("w:b"));
}
if ((options.boldComplexScript === undefined && options.bold) || options.boldComplexScript) {
- this.push(new BoldComplexScript());
+ this.push(new OnOffElement("w:bCs"));
}
if (options.italics) {
- this.push(new Italics());
+ this.push(new OnOffElement("w:i"));
}
if ((options.italicsComplexScript === undefined && options.italics) || options.italicsComplexScript) {
- this.push(new ItalicsComplexScript());
+ this.push(new OnOffElement("w:iCs"));
}
if (options.underline) {
@@ -100,32 +125,31 @@ export class RunProperties extends IgnoreIfEmptyXmlComponent {
}
if (options.size) {
- this.push(new Size(options.size));
+ this.push(new HpsMeasureElement("w:sz", options.size));
}
const szCs =
options.sizeComplexScript === undefined || options.sizeComplexScript === true ? options.size : options.sizeComplexScript;
if (szCs) {
- this.push(new SizeComplexScript(szCs));
+ this.push(new HpsMeasureElement("w:szCs", szCs));
}
if (options.rightToLeft) {
- this.push(new RightToLeft());
+ this.push(new OnOffElement("w:rtl"));
}
+ // These two are mutually exclusive
if (options.smallCaps) {
- this.push(new SmallCaps());
- }
-
- if (options.allCaps) {
- this.push(new Caps());
+ this.push(new OnOffElement("w:smallCaps"));
+ } else if (options.allCaps) {
+ this.push(new OnOffElement("w:caps"));
}
if (options.strike) {
- this.push(new Strike());
+ this.push(new OnOffElement("w:strike"));
}
if (options.doubleStrike) {
- this.push(new DoubleStrike());
+ this.push(new OnOffElement("w:dstrike"));
}
if (options.subScript) {
@@ -166,11 +190,11 @@ export class RunProperties extends IgnoreIfEmptyXmlComponent {
}
if (options.emboss) {
- this.push(new Emboss());
+ this.push(new OnOffElement("w:emboss"));
}
if (options.imprint) {
- this.push(new Imprint());
+ this.push(new OnOffElement("w:imprint"));
}
if (options.shading) {
diff --git a/src/file/paragraph/run/strike.spec.ts b/src/file/paragraph/run/strike.spec.ts
deleted file mode 100644
index 73c7e853c8..0000000000
--- a/src/file/paragraph/run/strike.spec.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import { expect } from "chai";
-
-import { Formatter } from "export/formatter";
-
-import { DoubleStrike, Strike } from "./formatting";
-
-describe("Strike", () => {
- let strike: Strike;
-
- beforeEach(() => {
- strike = new Strike();
- });
-
- describe("#constructor()", () => {
- it("should create a Strike with correct root key", () => {
- const tree = new Formatter().format(strike);
- expect(tree).to.deep.equal({
- "w:strike": {
- _attr: {
- "w:val": true,
- },
- },
- });
- });
- });
-});
-
-describe("DoubleStrike", () => {
- let strike: DoubleStrike;
-
- beforeEach(() => {
- strike = new DoubleStrike();
- });
-
- describe("#constructor()", () => {
- it("should create a Double Strike with correct root key", () => {
- const tree = new Formatter().format(strike);
- expect(tree).to.deep.equal({
- "w:dstrike": {
- _attr: {
- "w:val": true,
- },
- },
- });
- });
- });
-});
diff --git a/src/file/paragraph/run/symbol-run.spec.ts b/src/file/paragraph/run/symbol-run.spec.ts
index c41e6e1d51..8c6eb8e63c 100644
--- a/src/file/paragraph/run/symbol-run.spec.ts
+++ b/src/file/paragraph/run/symbol-run.spec.ts
@@ -49,7 +49,7 @@ describe("SymbolRun", () => {
emphasisMark: {
type: EmphasisMarkType.DOT,
},
- color: "green",
+ color: "00FF00",
size: 40,
highlight: "yellow",
});
@@ -65,7 +65,7 @@ describe("SymbolRun", () => {
{ "w:iCs": { _attr: { "w:val": true } } },
{ "w:u": { _attr: { "w:val": "double", "w:color": "red" } } },
{ "w:em": { _attr: { "w:val": "dot" } } },
- { "w:color": { _attr: { "w:val": "green" } } },
+ { "w:color": { _attr: { "w:val": "00FF00" } } },
{ "w:sz": { _attr: { "w:val": 40 } } },
{ "w:szCs": { _attr: { "w:val": 40 } } },
{ "w:highlight": { _attr: { "w:val": "yellow" } } },
diff --git a/src/file/paragraph/run/underline.ts b/src/file/paragraph/run/underline.ts
index 9cdb7b25a1..9433989d8f 100644
--- a/src/file/paragraph/run/underline.ts
+++ b/src/file/paragraph/run/underline.ts
@@ -21,7 +21,7 @@ export enum UnderlineType {
}
export abstract class BaseUnderline extends XmlComponent {
- constructor(underlineType: string, color?: string) {
+ constructor(underlineType: UnderlineType, color?: string) {
super("w:u");
this.root.push(
new Attributes({
@@ -40,96 +40,96 @@ export class Underline extends BaseUnderline {
export class DashUnderline extends BaseUnderline {
constructor() {
- super("dash");
+ super(UnderlineType.DASH);
}
}
export class DashDotDotHeavyUnderline extends BaseUnderline {
constructor() {
- super("dashDotDotHeavy");
+ super(UnderlineType.DASHDOTDOTHEAVY);
}
}
export class DashDotHeavyUnderline extends BaseUnderline {
constructor() {
- super("dashDotHeavy");
+ super(UnderlineType.DASHDOTHEAVY);
}
}
export class DashLongUnderline extends BaseUnderline {
constructor() {
- super("dashLong");
+ super(UnderlineType.DASHLONG);
}
}
export class DashLongHeavyUnderline extends BaseUnderline {
constructor() {
- super("dashLongHeavy");
+ super(UnderlineType.DASHLONGHEAVY);
}
}
export class DotDashUnderline extends BaseUnderline {
constructor() {
- super("dotDash");
+ super(UnderlineType.DOTDASH);
}
}
export class DotDotDashUnderline extends BaseUnderline {
constructor() {
- super("dotDotDash");
+ super(UnderlineType.DOTDOTDASH);
}
}
export class DottedUnderline extends BaseUnderline {
constructor() {
- super("dotted");
+ super(UnderlineType.DOTTED);
}
}
export class DottedHeavyUnderline extends BaseUnderline {
constructor() {
- super("dottedHeavy");
+ super(UnderlineType.DOTTEDHEAVY);
}
}
export class DoubleUnderline extends BaseUnderline {
constructor() {
- super("double");
+ super(UnderlineType.DOUBLE);
}
}
export class SingleUnderline extends BaseUnderline {
constructor() {
- super("single");
+ super(UnderlineType.SINGLE);
}
}
export class ThickUnderline extends BaseUnderline {
constructor() {
- super("thick");
+ super(UnderlineType.THICK);
}
}
export class WaveUnderline extends BaseUnderline {
constructor() {
- super("wave");
+ super(UnderlineType.WAVE);
}
}
export class WavyDoubleUnderline extends BaseUnderline {
constructor() {
- super("wavyDouble");
+ super(UnderlineType.WAVYDOUBLE);
}
}
export class WavyHeavyUnderline extends BaseUnderline {
constructor() {
- super("wavyHeavy");
+ super(UnderlineType.WAVYHEAVY);
}
}
export class WordsUnderline extends BaseUnderline {
constructor() {
- super("words");
+ super(UnderlineType.WORDS);
}
}
diff --git a/src/file/shading/shading.ts b/src/file/shading/shading.ts
index e00cedd5de..89f1cb49e0 100644
--- a/src/file/shading/shading.ts
+++ b/src/file/shading/shading.ts
@@ -18,6 +18,7 @@
//
//
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
+import { hexColorValue } from "../values";
export interface IShadingAttributesProperties {
readonly fill?: string;
@@ -34,9 +35,15 @@ class ShadingAttributes extends XmlAttributeComponent {
it("sets shading", () => {
const properties = new TableCellProperties({
shading: {
- fill: "test",
- color: "000",
+ fill: "ffffff",
+ color: "000000",
},
});
const tree = new Formatter().format(properties);
- expect(tree).to.deep.equal({ "w:tcPr": [{ "w:shd": { _attr: { "w:fill": "test", "w:color": "000" } } }] });
+ expect(tree).to.deep.equal({ "w:tcPr": [{ "w:shd": { _attr: { "w:fill": "ffffff", "w:color": "000000" } } }] });
});
it("should set the TableCellBorders", () => {
diff --git a/src/file/table/table-cell/table-cell.spec.ts b/src/file/table/table-cell/table-cell.spec.ts
index 51b452f347..f5ed48b97a 100644
--- a/src/file/table/table-cell/table-cell.spec.ts
+++ b/src/file/table/table-cell/table-cell.spec.ts
@@ -432,8 +432,8 @@ describe("TableCell", () => {
const cell = new TableCell({
children: [],
shading: {
- fill: "red",
- color: "blue",
+ fill: "FF0000",
+ color: "0000ff",
val: ShadingType.PERCENT_10,
},
});
@@ -447,8 +447,8 @@ describe("TableCell", () => {
{
"w:shd": {
_attr: {
- "w:color": "blue",
- "w:fill": "red",
+ "w:color": "0000ff",
+ "w:fill": "FF0000",
"w:val": "pct10",
},
},
diff --git a/src/file/values.ts b/src/file/values.ts
new file mode 100644
index 0000000000..a13b92c43d
--- /dev/null
+++ b/src/file/values.ts
@@ -0,0 +1,94 @@
+// Runtime checks and cleanup for value types in the spec that aren't easily expressed through our type system.
+// These will help us to prevent silent failures and corrupted documents.
+
+//
+//
+//
+//
+//
+export function universalMeasureValue(val: string): string {
+ const unit = val.slice(-2);
+ if (!universalMeasureUnits.includes(unit)) {
+ throw new Error(`Invalid unit '${unit}' specified. Valid units are ${universalMeasureUnits.join(", ")}`);
+ }
+ const amount = val.substring(0, val.length - 2);
+ if (isNaN(Number(amount))) {
+ throw new Error(`Invalid value '${amount}' specified. Expected a valid number.`);
+ }
+ return val;
+}
+const universalMeasureUnits = ["mm", "cm", "in", "pt", "pc", "pi"];
+
+//
+//
+//
+//
+//
+export function positiveUniversalMeasureValue(val: string): string {
+ const value = universalMeasureValue(val);
+ if (parseInt(value, 10) < 0) {
+ throw new Error(`Invalid value '${value}' specified. Expected a positive number.`);
+ }
+ return value;
+}
+
+//
+//
+//
+//
+//
+//
+//
+//
+
+// The xsd:hexBinary type represents binary data as a sequence of binary octets.
+// It uses hexadecimal encoding, where each binary octet is a two-character hexadecimal number.
+// Lowercase and uppercase letters A through F are permitted. For example, 0FB8 and 0fb8 are two
+// equal xsd:hexBinary representations consisting of two octets.
+//
+//
+//
+//
+//
+export function hexColorValue(val: string): string {
+ if (val === "auto") {
+ return val;
+ }
+ // It's super common to see colors prefixed with a pound, but technically invalid here.
+ // Most clients work with it, but strip it off anyway for strict compliance.
+ const color = val.charAt(0) === "#" ? val.substring(1) : val;
+ if (color.length !== 6 || isNaN(Number("0x" + color))) {
+ throw new Error(`Invalid color value '${color}'. Expected six digit hex value (eg FF9900)`);
+ }
+ return color;
+}
+
+//
+//
+//
+export function unsignedDecimalNumber(val: number): number {
+ if (isNaN(val) || val < 0) {
+ throw new Error(`Invalid value '${val}' specified. Must be a positive base10 integer.`);
+ }
+ return Math.floor(val);
+}
+
+//
+//
+//
+export function signedTwipsMeasureValue(val: string | number): string | number {
+ if (typeof val === "string") {
+ return universalMeasureValue(val);
+ }
+ if (isNaN(val)) {
+ throw new Error(`Invalid value '${val}' specified. Expected a valid number.`);
+ }
+ return Math.floor(val);
+}
+
+//
+//
+//
+export function hpsMeasureValue(val: string | number): string | number {
+ return typeof val === "string" ? positiveUniversalMeasureValue(val) : unsignedDecimalNumber(val);
+}
diff --git a/src/file/xml-components/index.ts b/src/file/xml-components/index.ts
index 295161b395..9ad7572069 100644
--- a/src/file/xml-components/index.ts
+++ b/src/file/xml-components/index.ts
@@ -4,4 +4,5 @@ export * from "./default-attributes";
export * from "./imported-xml-component";
export * from "./xmlable-object";
export * from "./initializable-xml-component";
+export * from "./simple-elements";
export * from "./base";
diff --git a/src/file/xml-components/simple-elements.ts b/src/file/xml-components/simple-elements.ts
new file mode 100644
index 0000000000..8fa665a832
--- /dev/null
+++ b/src/file/xml-components/simple-elements.ts
@@ -0,0 +1,27 @@
+import { Attributes, XmlComponent } from "file/xml-components";
+
+import { hpsMeasureValue } from "../values";
+
+// This represents element type CT_OnOff, which indicate a boolean value.
+//
+//
+//
+//
+export class OnOffElement extends XmlComponent {
+ constructor(name: string, val: boolean | undefined = true) {
+ super(name);
+ this.root.push(new Attributes({ val }));
+ }
+}
+
+// This represents element type CT_HpsMeasure, which indicate an unsigned int or a measurement with unit.
+//
+//
+//
+//
+export class HpsMeasureElement extends XmlComponent {
+ constructor(name: string, val: number | string) {
+ super(name);
+ this.root.push(new Attributes({ val: hpsMeasureValue(val) }));
+ }
+}