diff --git a/CHANGELOG.md b/CHANGELOG.md
index a67571cc..1be7f5ae 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- **style:** add common styles
- **style:** allow page break to be before or after a paragraph, closes [#31](https://github.com/connium/simple-odf/issues/31)
- **style:** add keep-with-next flag
+- **style:** add orphan & widdow control, paragraph background, line height
- **docs:** add a realistic example
### Changed
diff --git a/README.md b/README.md
index 7d4d2446..9000e134 100644
--- a/README.md
+++ b/README.md
@@ -87,6 +87,7 @@ See the [examples](./examples/README.md) for more details on how to use the libr
Learn more about the [OASIS Open Document Format](http://docs.oasis-open.org/office/v1.2/OpenDocument-v1.2.html).
+- [Features](./docs/Features.md)
- [API reference](./docs/API.md)
## Contributing
diff --git a/docs/Features.md b/docs/Features.md
new file mode 100644
index 00000000..734879f0
--- /dev/null
+++ b/docs/Features.md
@@ -0,0 +1,101 @@
+# Features
+
+- Metadata
+- Font face declaration
+- [Style](#style)
+ - [Character](#character)
+ - [Paragraph](#paragraph)
+- [Text content](#text-content)
+
+## Style
+
+### Character
+- Font
+ - :heavy_check_mark: Family
+ - :heavy_check_mark: Style
+ - :heavy_check_mark: Size
+ - :x: Language
+- Font effects
+ - :heavy_check_mark: Font color
+ - :heavy_check_mark: Effects
+ - :x: Relief
+ - :soon: Overlining
+ - :soon: Strikethrough
+ - :soon: Underlining
+- Position
+ - :x: Position
+ - :x: Rotation & scaling
+ - :x: Spacing
+- Hyperlink
+ - :heavy_check_mark: Hyperlink
+ - :x: Events
+ - :x: Character Styles
+- Highlighting
+ - :x: Color
+- Borders
+ - :x: Line arrangement
+ - :x: Line
+ - :x: Spacing to contents
+ - :x: Shadow style
+
+### Paragraph
+- Indents & spacing
+ - :soon: Indent
+ - :soon: Spacing
+ - :construction: Line spacing
+- Alignment
+ - :heavy_check_mark: horizontal
+ - :construction: vertical
+- Text flow
+ - :x: Hyphenation
+ - :heavy_check_mark: Breaks
+ - :construction: Options
+- Outline & numbering
+ - :x: Outline
+ - :x: Numbering
+ - :x: Line numbering
+- Tabs
+ - :heavy_check_mark: Type
+ - :soon: Fill character
+- Drop caps
+- Borders
+ - :soon: Line arrangement
+ - :soon: Line
+ - :x: Spacing to contents
+ - :x: Shadow style
+- Area
+ - :construction: Color
+ - :x: Gradient
+ - :x: Hatching
+ - :x: Bitmap
+
+## Text content
+
+- :heavy_check_mark: Heading
+- :heavy_check_mark: Paragraph
+ - :heavy_check_mark: tab
+ - :heavy_check_mark: line break
+ - :x: hyphen
+ - :soon: span
+ - :construction: hyperlink
+ - :x: number
+- :construction: List
+- :x: Section
+- :x: Change Tracking
+- :x: Bookmark & Reference
+- :x: Note
+- :x: Text field
+- :x: Table
+- :x: Graphic content
+ - :x: Drawing shape
+ - :x: Frame
+ - :x: 3D Shape
+- :x: Chart
+- :x: Form
+
+
diff --git a/src/api/office/AutomaticStyles.spec.ts b/src/api/office/AutomaticStyles.spec.ts
index ebd87b1d..ee205184 100644
--- a/src/api/office/AutomaticStyles.spec.ts
+++ b/src/api/office/AutomaticStyles.spec.ts
@@ -1,4 +1,4 @@
-import { ParagraphStyle } from '../style';
+import { ParagraphStyle, TabStopType } from '../style';
import { AutomaticStyles } from './AutomaticStyles';
describe(AutomaticStyles.name, () => {
@@ -9,6 +9,7 @@ describe(AutomaticStyles.name, () => {
beforeEach(() => {
testStyle1 = new ParagraphStyle();
testStyle2 = new ParagraphStyle().setFontSize(23);
+ testStyle2.addTabStop(42, TabStopType.Right);
automaticStyles = new AutomaticStyles();
});
diff --git a/src/api/office/AutomaticStyles.ts b/src/api/office/AutomaticStyles.ts
index 6fa7a76b..ff924ef2 100644
--- a/src/api/office/AutomaticStyles.ts
+++ b/src/api/office/AutomaticStyles.ts
@@ -114,9 +114,17 @@ export class AutomaticStyles implements IStyles {
const paragraphStyle = style as ParagraphStyle;
// paragraph properties
+ const backgroundColor = paragraphStyle.getBackgroundColor();
+ hash.update(backgroundColor !== undefined ? backgroundColor.toHex() : '');
hash.update(paragraphStyle.getHorizontalAlignment());
hash.update(paragraphStyle.getKeepTogether() ? 'kt' : '');
+ hash.update(paragraphStyle.getKeepWithNext() ? 'kwn' : '');
+ hash.update('lh' + (paragraphStyle.getLineHeight() || ''));
+ hash.update('lhal' + (paragraphStyle.getLineHeightAtLeast() || ''));
+ hash.update('orphans' + (paragraphStyle.getOrphans() || ''));
hash.update(paragraphStyle.getPageBreak().toString());
+ hash.update(paragraphStyle.getVerticalAlignment());
+ hash.update('widows' + (paragraphStyle.getWidows() || ''));
paragraphStyle.getTabStops().forEach((tabStop) => {
hash.update(`tab${tabStop.getPosition()}${tabStop.getType()}`);
});
diff --git a/src/api/style/IParagraphProperties.ts b/src/api/style/IParagraphProperties.ts
index 5040f440..7bd328c9 100644
--- a/src/api/style/IParagraphProperties.ts
+++ b/src/api/style/IParagraphProperties.ts
@@ -1,7 +1,9 @@
+import { Color } from './Color';
import { HorizontalAlignment } from './HorizontalAlignment';
import { PageBreak } from './PageBreak';
import { TabStop } from './TabStop';
import { TabStopType } from './TabStopType';
+import { VerticalAlignment } from './VerticalAlignment';
/**
* This class represents the styling properties of a paragraph.
@@ -11,6 +13,16 @@ import { TabStopType } from './TabStopType';
* @since 0.4.0
*/
export interface IParagraphProperties {
+ /**
+ * @since 0.9.0
+ */
+ setBackgroundColor (color: Color | undefined): void;
+
+ /**
+ * @since 0.9.0
+ */
+ getBackgroundColor (): Color | undefined;
+
/**
* Sets the horizontal alignment setting of this paragraph.
*
@@ -43,20 +55,45 @@ export interface IParagraphProperties {
getKeepTogether (): boolean;
/**
- * TODO
- *
* @since 0.9.0
*/
setKeepWithNext (keepWithNext?: boolean): void;
/**
- * TODO
- *
- * @returns {boolean} `true` TODO, `false` otherwise
* @since 0.9.0
*/
getKeepWithNext (): boolean;
+ /**
+ * @since 0.9.0
+ */
+ setLineHeight (lineHeight: number | string | undefined): void;
+
+ /**
+ * @since 0.9.0
+ */
+ getLineHeight (): number | string | undefined;
+
+ /**
+ * @since 0.9.0
+ */
+ setLineHeightAtLeast (minimumLineHeight: number | undefined): void;
+
+ /**
+ * @since 0.9.0
+ */
+ getLineHeightAtLeast (): number | undefined;
+
+ /**
+ * @since 0.9.0
+ */
+ setOrphans (orphans: number | undefined): void;
+
+ /**
+ * @since 0.9.0
+ */
+ getOrphans (): number | undefined;
+
/**
* Sets the page break setting of the paragraph.
*
@@ -73,6 +110,26 @@ export interface IParagraphProperties {
*/
getPageBreak (): PageBreak;
+ /**
+ * @since 0.9.0
+ */
+ setVerticalAlignment (verticalAlignment: VerticalAlignment): void;
+
+ /**
+ * @since 0.9.0
+ */
+ getVerticalAlignment (): VerticalAlignment;
+
+ /**
+ * @since 0.9.0
+ */
+ setWidows (widows: number | undefined): void;
+
+ /**
+ * @since 0.9.0
+ */
+ getWidows (): number | undefined;
+
/**
* Adds a new tab stop to this style.
* If a tab stop at the same position already exists, the new tab stop will not be added.
diff --git a/src/api/style/ParagraphProperties.spec.ts b/src/api/style/ParagraphProperties.spec.ts
index b936036b..d2c5fbdd 100644
--- a/src/api/style/ParagraphProperties.spec.ts
+++ b/src/api/style/ParagraphProperties.spec.ts
@@ -1,8 +1,10 @@
+import { Color } from './Color';
import { HorizontalAlignment } from './HorizontalAlignment';
import { PageBreak } from './PageBreak';
import { ParagraphProperties } from './ParagraphProperties';
import { TabStop } from './TabStop';
import { TabStopType } from './TabStopType';
+import { VerticalAlignment } from './VerticalAlignment';
describe(ParagraphProperties.name, () => {
let properties: ParagraphProperties;
@@ -11,6 +13,20 @@ describe(ParagraphProperties.name, () => {
properties = new ParagraphProperties();
});
+ describe('background color', () => {
+ it('return undefined by default', () => {
+ expect(properties.getBackgroundColor()).toBeUndefined();
+ });
+
+ it('return previously set alignment', () => {
+ const testColor = Color.fromRgb(1, 2, 3);
+
+ properties.setBackgroundColor(testColor);
+
+ expect(properties.getBackgroundColor()).toBe(testColor);
+ });
+ });
+
describe('horizontal alignment', () => {
it('return `Default` by default', () => {
expect(properties.getHorizontalAlignment()).toBe(HorizontalAlignment.Default);
@@ -55,6 +71,99 @@ describe(ParagraphProperties.name, () => {
});
});
+ describe('line height', () => {
+ const testLineHeightNumber = 23;
+ const testLineHeightPercent = '42%';
+
+ it('return undefined by default', () => {
+ expect(properties.getLineHeight()).toBeUndefined();
+ });
+
+ it('return previously set state', () => {
+ properties.setLineHeight(testLineHeightNumber);
+
+ expect(properties.getLineHeight()).toBe(testLineHeightNumber);
+
+ properties.setLineHeight(testLineHeightPercent);
+
+ expect(properties.getLineHeight()).toBe(testLineHeightPercent);
+
+ properties.setLineHeight(undefined);
+
+ expect(properties.getLineHeight()).toBeUndefined();
+ });
+
+ it('ignore invalid value', () => {
+ properties.setLineHeight(testLineHeightNumber);
+
+ properties.setLineHeight(0);
+
+ expect(properties.getLineHeight()).toBe(testLineHeightNumber);
+
+ properties.setLineHeight('42$');
+
+ expect(properties.getLineHeight()).toBe(testLineHeightNumber);
+ });
+ });
+
+ describe('line height at least', () => {
+ const testLineHeight = 23;
+
+ it('return undefined by default', () => {
+ expect(properties.getLineHeightAtLeast()).toBeUndefined();
+ });
+
+ it('return previously set state', () => {
+ properties.setLineHeightAtLeast(testLineHeight);
+
+ expect(properties.getLineHeightAtLeast()).toBe(testLineHeight);
+
+ properties.setLineHeightAtLeast(undefined);
+
+ expect(properties.getLineHeightAtLeast()).toBeUndefined();
+ });
+
+ it('ignore invalid value', () => {
+ properties.setLineHeightAtLeast(testLineHeight);
+
+ properties.setLineHeightAtLeast(0);
+
+ expect(properties.getLineHeightAtLeast()).toBe(testLineHeight);
+ });
+ });
+
+ describe('orphans', () => {
+ const testOrphans = 23;
+
+ it('return undefined by default', () => {
+ expect(properties.getOrphans()).toBeUndefined();
+ });
+
+ it('return previously set state', () => {
+ properties.setOrphans(testOrphans);
+
+ expect(properties.getOrphans()).toBe(testOrphans);
+
+ properties.setOrphans(undefined);
+
+ expect(properties.getOrphans()).toBeUndefined();
+ });
+
+ it('use truncated value', () => {
+ properties.setOrphans(23.42);
+
+ expect(properties.getOrphans()).toBe(testOrphans);
+ });
+
+ it('ignore invalid value', () => {
+ properties.setOrphans(testOrphans);
+
+ properties.setOrphans(-23);
+
+ expect(properties.getOrphans()).toBe(testOrphans);
+ });
+ });
+
describe('page break', () => {
it('return None by default', () => {
expect(properties.getPageBreak()).toBe(PageBreak.None);
@@ -71,6 +180,50 @@ describe(ParagraphProperties.name, () => {
});
});
+ describe('vertical alignment', () => {
+ it('return Default by default', () => {
+ expect(properties.getVerticalAlignment()).toBe(VerticalAlignment.Default);
+ });
+
+ it('return previously set alignment', () => {
+ properties.setVerticalAlignment(VerticalAlignment.Middle);
+
+ expect(properties.getVerticalAlignment()).toBe(VerticalAlignment.Middle);
+ });
+ });
+
+ describe('widows', () => {
+ const testWidows = 23;
+
+ it('return undefined by default', () => {
+ expect(properties.getWidows()).toBeUndefined();
+ });
+
+ it('return previously set state', () => {
+ properties.setWidows(testWidows);
+
+ expect(properties.getWidows()).toBe(testWidows);
+
+ properties.setWidows(undefined);
+
+ expect(properties.getWidows()).toBeUndefined();
+ });
+
+ it('use truncated value', () => {
+ properties.setWidows(23.42);
+
+ expect(properties.getWidows()).toBe(testWidows);
+ });
+
+ it('ignore invalid value', () => {
+ properties.setWidows(testWidows);
+
+ properties.setWidows(-23);
+
+ expect(properties.getWidows()).toBe(testWidows);
+ });
+ });
+
describe('#addTabStop', () => {
it('add new item to the list of tab stops by position and return the added tab stop', () => {
const testTabStop = new TabStop(23);
diff --git a/src/api/style/ParagraphProperties.ts b/src/api/style/ParagraphProperties.ts
index d89b3a5d..7c3dafa5 100644
--- a/src/api/style/ParagraphProperties.ts
+++ b/src/api/style/ParagraphProperties.ts
@@ -1,19 +1,29 @@
+import { isNonNegativeNumber, isPercent } from '../util';
+import { Color } from './Color';
import { HorizontalAlignment } from './HorizontalAlignment';
import { IParagraphProperties } from './IParagraphProperties';
import { PageBreak } from './PageBreak';
import { TabStopType } from './TabStopType';
import { TabStop } from './TabStop';
+import { VerticalAlignment } from './VerticalAlignment';
const DEFAULT_HORIZONTAL_ALIGNMENT = HorizontalAlignment.Default;
-const DEFAULT_PAGE_BREAK = PageBreak.None;
const DEFAULT_KEEP_TOGETHER = false;
const DEFAULT_KEEP_WITH_NEXT = false;
+const DEFAULT_PAGE_BREAK = PageBreak.None;
+const DEFAULT_VERTICAL_ALIGNMENT = VerticalAlignment.Default;
export class ParagraphProperties implements IParagraphProperties {
+ private backgroundColor: Color | undefined;
private horizontalAlignment: HorizontalAlignment;
+ private lineHeight: number | string | undefined;
+ private minimumLineHeight: number | undefined;
+ private orphans: number | undefined;
private pageBreak: PageBreak;
private shouldKeepTogether: boolean;
private shouldKeepWithNext: boolean;
+ private verticalAlignment: VerticalAlignment;
+ private widows: number | undefined;
private tabStops: TabStop[] = [];
public constructor () {
@@ -21,6 +31,17 @@ export class ParagraphProperties implements IParagraphProperties {
this.pageBreak = DEFAULT_PAGE_BREAK;
this.shouldKeepTogether = DEFAULT_KEEP_TOGETHER;
this.shouldKeepWithNext = DEFAULT_KEEP_WITH_NEXT;
+ this.verticalAlignment = DEFAULT_VERTICAL_ALIGNMENT;
+ }
+
+ /** @inheritdoc */
+ public setBackgroundColor (color: Color | undefined): void {
+ this.backgroundColor = color;
+ }
+
+ /** @inheritdoc */
+ public getBackgroundColor (): Color | undefined {
+ return this.backgroundColor;
}
/** @inheritdoc */
@@ -44,15 +65,58 @@ export class ParagraphProperties implements IParagraphProperties {
}
/** @inheritdoc */
- setKeepWithNext (keepWithNext = true): void {
+ public setKeepWithNext (keepWithNext = true): void {
this.shouldKeepWithNext = keepWithNext;
}
/** @inheritdoc */
- getKeepWithNext (): boolean {
+ public getKeepWithNext (): boolean {
return this.shouldKeepWithNext;
}
+ /** @inheritdoc */
+ public setLineHeight (lineHeight: number | string | undefined): void {
+ if (isNonNegativeNumber(lineHeight) || isPercent(lineHeight) || lineHeight === undefined) {
+ this.lineHeight = lineHeight;
+ this.minimumLineHeight = undefined;
+ }
+ }
+
+ /** @inheritdoc */
+ public getLineHeight (): number | string | undefined {
+ return this.lineHeight;
+ }
+
+ /** @inheritdoc */
+ public setLineHeightAtLeast (minimumLineHeight: number | undefined): void {
+ if (isNonNegativeNumber(minimumLineHeight) || minimumLineHeight === undefined) {
+ this.minimumLineHeight = minimumLineHeight;
+ this.lineHeight = undefined;
+ }
+ }
+
+ /** @inheritdoc */
+ public getLineHeightAtLeast (): number | undefined {
+ return this.minimumLineHeight;
+ }
+
+ /** @inheritdoc */
+ public setOrphans (orphans: number | undefined): void {
+ if (isNonNegativeNumber(orphans)) {
+ this.orphans = Math.trunc(orphans as number);
+ return;
+ }
+
+ if (orphans === undefined) {
+ this.orphans = orphans;
+ }
+ }
+
+ /** @inheritdoc */
+ public getOrphans (): number | undefined {
+ return this.orphans;
+ }
+
/** @inheritdoc */
public setPageBreak (pageBreak: PageBreak): void {
this.pageBreak = pageBreak;
@@ -63,6 +127,33 @@ export class ParagraphProperties implements IParagraphProperties {
return this.pageBreak;
}
+ /** @inheritdoc */
+ public setVerticalAlignment (verticalAlignment: VerticalAlignment): void {
+ this.verticalAlignment = verticalAlignment;
+ }
+
+ /** @inheritdoc */
+ public getVerticalAlignment (): VerticalAlignment {
+ return this.verticalAlignment;
+ }
+
+ /** @inheritdoc */
+ public setWidows (widows: number | undefined): void {
+ if (isNonNegativeNumber(widows)) {
+ this.widows = Math.trunc(widows as number);
+ return;
+ }
+
+ if (widows === undefined) {
+ this.widows = widows;
+ }
+ }
+
+ /** @inheritdoc */
+ public getWidows (): number | undefined {
+ return this.widows;
+ }
+
/** @inheritdoc */
public addTabStop (position: number, type: TabStopType): TabStop | undefined;
diff --git a/src/api/style/ParagraphStyle.ts b/src/api/style/ParagraphStyle.ts
index a8a721d5..b3a0af55 100644
--- a/src/api/style/ParagraphStyle.ts
+++ b/src/api/style/ParagraphStyle.ts
@@ -11,6 +11,7 @@ import { TabStopType } from './TabStopType';
import { TextProperties } from './TextProperties';
import { TextTransformation } from './TextTransformation';
import { Typeface } from './Typeface';
+import { VerticalAlignment } from './VerticalAlignment';
export class ParagraphStyle extends Style implements IParagraphProperties, ITextProperties {
private paragraphProperties: ParagraphProperties;
@@ -25,6 +26,18 @@ export class ParagraphStyle extends Style implements IParagraphProperties, IText
// paragraph properties
+ /** @inheritdoc */
+ public setBackgroundColor (color: Color | undefined): ParagraphStyle {
+ this.paragraphProperties.setBackgroundColor(color);
+
+ return this;
+ }
+
+ /** @inheritdoc */
+ public getBackgroundColor (): Color | undefined {
+ return this.paragraphProperties.getBackgroundColor();
+ }
+
/** @inheritdoc */
public setHorizontalAlignment (horizontalAlignment: HorizontalAlignment): ParagraphStyle {
this.paragraphProperties.setHorizontalAlignment(horizontalAlignment);
@@ -61,6 +74,42 @@ export class ParagraphStyle extends Style implements IParagraphProperties, IText
return this.paragraphProperties.getKeepWithNext();
}
+ /** @inheritdoc */
+ public setLineHeight (lineHeight: number | string | undefined): ParagraphStyle {
+ this.paragraphProperties.setLineHeight(lineHeight);
+
+ return this;
+ }
+
+ /** @inheritdoc */
+ public getLineHeight (): number | string | undefined {
+ return this.paragraphProperties.getLineHeight();
+ }
+
+ /** @inheritdoc */
+ public setLineHeightAtLeast (minimumLineHeight: number | undefined): ParagraphStyle {
+ this.paragraphProperties.setLineHeightAtLeast(minimumLineHeight);
+
+ return this;
+ }
+
+ /** @inheritdoc */
+ public getLineHeightAtLeast (): number | undefined {
+ return this.paragraphProperties.getLineHeightAtLeast();
+ }
+
+ /** @inheritdoc */
+ public setOrphans (orphans: number | undefined): ParagraphStyle {
+ this.paragraphProperties.setOrphans(orphans);
+
+ return this;
+ }
+
+ /** @inheritdoc */
+ public getOrphans (): number | undefined {
+ return this.paragraphProperties.getOrphans();
+ }
+
/** @inheritdoc */
public setPageBreak (pageBreak: PageBreak): ParagraphStyle {
this.paragraphProperties.setPageBreak(pageBreak);
@@ -73,6 +122,30 @@ export class ParagraphStyle extends Style implements IParagraphProperties, IText
return this.paragraphProperties.getPageBreak();
}
+ /** @inheritdoc */
+ public setVerticalAlignment (verticalAlignment: VerticalAlignment): ParagraphStyle {
+ this.paragraphProperties.setVerticalAlignment(verticalAlignment);
+
+ return this;
+ }
+
+ /** @inheritdoc */
+ public getVerticalAlignment (): VerticalAlignment {
+ return this.paragraphProperties.getVerticalAlignment();
+ }
+
+ /** @inheritdoc */
+ public setWidows (widows: number | undefined): ParagraphStyle {
+ this.paragraphProperties.setWidows(widows);
+
+ return this;
+ }
+
+ /** @inheritdoc */
+ public getWidows (): number | undefined {
+ return this.paragraphProperties.getWidows();
+ }
+
/** @inheritdoc */
public addTabStop (position: number, type: TabStopType): TabStop | undefined;
diff --git a/src/api/style/VerticalAlignment.ts b/src/api/style/VerticalAlignment.ts
new file mode 100644
index 00000000..a027d052
--- /dev/null
+++ b/src/api/style/VerticalAlignment.ts
@@ -0,0 +1,7 @@
+export enum VerticalAlignment {
+ Default = '',
+ Top = 'top',
+ Middle = 'middle',
+ Bottom = 'bottom',
+ Baseline = 'baseline'
+}
diff --git a/src/api/style/index.ts b/src/api/style/index.ts
index 79317928..d1b44b89 100644
--- a/src/api/style/index.ts
+++ b/src/api/style/index.ts
@@ -13,3 +13,4 @@ export { TabStopType } from './TabStopType';
export { TextProperties } from './TextProperties';
export { TextTransformation } from './TextTransformation';
export { Typeface } from './Typeface';
+export { VerticalAlignment } from './VerticalAlignment';
diff --git a/src/api/util/index.ts b/src/api/util/index.ts
new file mode 100644
index 00000000..885d4fec
--- /dev/null
+++ b/src/api/util/index.ts
@@ -0,0 +1,2 @@
+export { isNonNegativeNumber } from './isNonNegativeNumber';
+export { isPercent } from './isPercent';
diff --git a/src/api/util/isNonNegativeNumber.ts b/src/api/util/isNonNegativeNumber.ts
new file mode 100644
index 00000000..c84b77c8
--- /dev/null
+++ b/src/api/util/isNonNegativeNumber.ts
@@ -0,0 +1,10 @@
+/**
+ * The `isNonNegativeNumber` method returns whether the given value is a non-negative number.
+ *
+ * @param {any} value The value that is to be checked
+ * @returns {boolean} `true` if the given value is a non-negative number, `false` otherwise
+ * @private
+ */
+export function isNonNegativeNumber (value: any): boolean {
+ return typeof value === 'number' && value > 0;
+}
diff --git a/src/api/util/isPercent.ts b/src/api/util/isPercent.ts
new file mode 100644
index 00000000..923dd891
--- /dev/null
+++ b/src/api/util/isPercent.ts
@@ -0,0 +1,10 @@
+/**
+ * The `isPercent` method returns whether the given value is a percentage.
+ *
+ * @param {any} value The value that is to be checked
+ * @returns {boolean} `true` if the given value is a percentage, `false` otherwise
+ * @private
+ */
+export function isPercent (value: any): boolean {
+ return typeof value === 'string' && /^-?([0-9]+(\.[0-9]*)?|\.[0-9]+)%$/.test(value);
+}
diff --git a/src/index.ts b/src/index.ts
index 5a30f88a..befd2afe 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -25,6 +25,7 @@ export { TabStop } from './api/style/TabStop';
export { TabStopType } from './api/style/TabStopType';
export { TextTransformation } from './api/style/TextTransformation';
export { Typeface } from './api/style/Typeface';
+export { VerticalAlignment } from './api/style/VerticalAlignment';
// text
export { Heading } from './api/text/Heading';
diff --git a/src/xml/OdfAttributeName.ts b/src/xml/OdfAttributeName.ts
index 1cdcd89d..c4e64d33 100644
--- a/src/xml/OdfAttributeName.ts
+++ b/src/xml/OdfAttributeName.ts
@@ -1,23 +1,29 @@
export enum OdfAttributeName {
+ FormatBackgroundColor = 'fo:background-color',
FormatBreakAfter = 'fo:break-after',
FormatBreakBefore = 'fo:break-before',
- FormatKeepTogether = 'fo:keep-together',
- FormatKeepWithNext = 'fo:keep-with-next',
FormatColor = 'fo:color',
FormatFontSize = 'fo:font-size',
FormatFontStyle = 'fo:font-style',
FormatFontWeight = 'fo:font-weight',
+ FormatKeepTogether = 'fo:keep-together',
+ FormatKeepWithNext = 'fo:keep-with-next',
+ FormatLineHeight = 'fo:line-height',
+ FormatOrphans = 'fo:orphans',
FormatTextAlign = 'fo:text-align',
FormatTextTransform = 'fo:text-transform',
+ FormatWidows = 'fo:widows',
OfficeMimetype = 'office:mimetype',
OfficeVersion = 'office:version',
StyleFamily = 'style:family',
StyleFontName = 'style:font-name',
+ StyleLineHeightAtLeast = 'style:line-height-at-least',
StyleName = 'style:name',
StylePosition = 'style:position',
StyleType = 'style:type',
+ StyleVerticalAlign = 'style:vertical-align',
SvgHeight = 'svg:height',
SvgWidth = 'svg:width',
diff --git a/src/xml/office/StylesWriter.spec.ts b/src/xml/office/StylesWriter.spec.ts
index 7c757bf8..d8412927 100644
--- a/src/xml/office/StylesWriter.spec.ts
+++ b/src/xml/office/StylesWriter.spec.ts
@@ -2,7 +2,7 @@ import { DOMImplementation, XMLSerializer } from 'xmldom';
import { CommonStyles, AutomaticStyles } from '../../api/office';
import { Color, HorizontalAlignment, PageBreak, ParagraphStyle, TextTransformation, Typeface } from '../../api/style';
// tslint:disable-next-line:no-duplicate-imports
-import { TabStop, TabStopType } from '../../api/style';
+import { TabStop, TabStopType, VerticalAlignment } from '../../api/style';
import { OdfElementName } from '../OdfElementName';
import { StylesWriter } from './StylesWriter';
@@ -77,6 +77,15 @@ describe(StylesWriter.name, () => {
testStyle = commonStyles.createParagraphStyle('Summary');
});
+ it('set background color', () => {
+ testStyle.setBackgroundColor(Color.fromRgb(1, 2, 3));
+
+ stylesWriter.write(commonStyles, testDocument, testRoot);
+ const documentAsString = new XMLSerializer().serializeToString(testDocument);
+
+ expect(documentAsString).toMatch(//);
+ });
+
it('set horizontal alignment', () => {
testStyle.setHorizontalAlignment(HorizontalAlignment.Center);
@@ -104,6 +113,42 @@ describe(StylesWriter.name, () => {
expect(documentAsString).toMatch(//);
});
+ it('set line height as fix value', () => {
+ testStyle.setLineHeight(23);
+
+ stylesWriter.write(commonStyles, testDocument, testRoot);
+ const documentAsString = new XMLSerializer().serializeToString(testDocument);
+
+ expect(documentAsString).toMatch(//);
+ });
+
+ it('set line height as percentage', () => {
+ testStyle.setLineHeight('42%');
+
+ stylesWriter.write(commonStyles, testDocument, testRoot);
+ const documentAsString = new XMLSerializer().serializeToString(testDocument);
+
+ expect(documentAsString).toMatch(//);
+ });
+
+ it('set line height at least', () => {
+ testStyle.setLineHeightAtLeast(23);
+
+ stylesWriter.write(commonStyles, testDocument, testRoot);
+ const documentAsString = new XMLSerializer().serializeToString(testDocument);
+
+ expect(documentAsString).toMatch(//);
+ });
+
+ it('set orphans', () => {
+ testStyle.setOrphans(23);
+
+ stylesWriter.write(commonStyles, testDocument, testRoot);
+ const documentAsString = new XMLSerializer().serializeToString(testDocument);
+
+ expect(documentAsString).toMatch(//);
+ });
+
it('set page break before', () => {
testStyle.setPageBreak(PageBreak.Before);
@@ -122,6 +167,24 @@ describe(StylesWriter.name, () => {
expect(documentAsString).toMatch(//);
});
+ it('set vertical alignment', () => {
+ testStyle.setVerticalAlignment(VerticalAlignment.Middle);
+
+ stylesWriter.write(commonStyles, testDocument, testRoot);
+ const documentAsString = new XMLSerializer().serializeToString(testDocument);
+
+ expect(documentAsString).toMatch(//);
+ });
+
+ it('set widows', () => {
+ testStyle.setWidows(23);
+
+ stylesWriter.write(commonStyles, testDocument, testRoot);
+ const documentAsString = new XMLSerializer().serializeToString(testDocument);
+
+ expect(documentAsString).toMatch(//);
+ });
+
it('set tab stops', () => {
testStyle.addTabStop(new TabStop(2, TabStopType.Center));
testStyle.addTabStop(new TabStop(4, TabStopType.Char));
diff --git a/src/xml/office/StylesWriter.ts b/src/xml/office/StylesWriter.ts
index 862ef938..19978bd2 100644
--- a/src/xml/office/StylesWriter.ts
+++ b/src/xml/office/StylesWriter.ts
@@ -1,13 +1,15 @@
+// tslint:disable:no-duplicate-imports
import { AutomaticStyles, CommonStyles, IStyles } from '../../api/office';
import { HorizontalAlignment, ParagraphStyle, Style, StyleFamily, TabStopType, PageBreak } from '../../api/style';
-// tslint:disable-next-line:no-duplicate-imports
-import { TextTransformation, Typeface } from '../../api/style';
+import { TextTransformation, Typeface, VerticalAlignment } from '../../api/style';
import { OdfAttributeName } from '../OdfAttributeName';
import { OdfElementName } from '../OdfElementName';
/**
* Transforms a {@link StyleManager} object into ODF conform XML
*
+ * NOTE: The properties are set in the order of their appearance in the Realx NG schema.
+ *
* @since 0.9.0
*/
export class StylesWriter {
@@ -68,6 +70,23 @@ export class StylesWriter {
const paragraphPropertiesElement = document.createElement(OdfElementName.StyleParagraphProperties);
parent.appendChild(paragraphPropertiesElement);
+ const lineHeight = style.getLineHeight();
+ switch (typeof lineHeight) {
+ case 'number':
+ paragraphPropertiesElement.setAttribute(OdfAttributeName.FormatLineHeight, lineHeight + 'mm');
+ break;
+ case 'string':
+ paragraphPropertiesElement.setAttribute(OdfAttributeName.FormatLineHeight, lineHeight);
+ break;
+ default:
+ break;
+ }
+
+ const lineHeightAtLeast = style.getLineHeightAtLeast();
+ if (lineHeightAtLeast !== undefined) {
+ paragraphPropertiesElement.setAttribute(OdfAttributeName.StyleLineHeightAtLeast, lineHeightAtLeast + 'mm');
+ }
+
if (style.getHorizontalAlignment() !== HorizontalAlignment.Default) {
paragraphPropertiesElement.setAttribute(OdfAttributeName.FormatTextAlign, style.getHorizontalAlignment());
}
@@ -76,8 +95,14 @@ export class StylesWriter {
paragraphPropertiesElement.setAttribute(OdfAttributeName.FormatKeepTogether, 'always');
}
- if (style.getKeepWithNext() === true) {
- paragraphPropertiesElement.setAttribute(OdfAttributeName.FormatKeepWithNext, 'always');
+ const widows = style.getWidows();
+ if (widows !== undefined) {
+ paragraphPropertiesElement.setAttribute(OdfAttributeName.FormatWidows, widows.toString(10));
+ }
+
+ const orphans = style.getOrphans();
+ if (orphans !== undefined) {
+ paragraphPropertiesElement.setAttribute(OdfAttributeName.FormatOrphans, orphans.toString(10));
}
switch (style.getPageBreak()) {
@@ -91,6 +116,19 @@ export class StylesWriter {
break;
}
+ const backgroundColor = style.getBackgroundColor();
+ if (backgroundColor !== undefined) {
+ paragraphPropertiesElement.setAttribute(OdfAttributeName.FormatBackgroundColor, backgroundColor.toHex());
+ }
+
+ if (style.getKeepWithNext() === true) {
+ paragraphPropertiesElement.setAttribute(OdfAttributeName.FormatKeepWithNext, 'always');
+ }
+
+ if (style.getVerticalAlignment() !== VerticalAlignment.Default) {
+ paragraphPropertiesElement.setAttribute(OdfAttributeName.StyleVerticalAlign, style.getVerticalAlignment());
+ }
+
const tabStops = style.getTabStops();
if (tabStops.length === 0) {
return paragraphPropertiesElement;
@@ -103,7 +141,7 @@ export class StylesWriter {
const tabStopElement = document.createElement(OdfElementName.StyleTabStop);
parent.appendChild(tabStopElement);
- tabStopElement.setAttribute(OdfAttributeName.StylePosition, `${tabStop.getPosition()}mm`);
+ tabStopElement.setAttribute(OdfAttributeName.StylePosition, tabStop.getPosition() + 'mm');
if (tabStop.getType() !== TabStopType.Left) {
tabStopElement.setAttribute(OdfAttributeName.StyleType, tabStop.getType());
}
diff --git a/test/integration.spec.ts b/test/integration.spec.ts
index bd252161..ed9153e9 100644
--- a/test/integration.spec.ts
+++ b/test/integration.spec.ts
@@ -1,10 +1,11 @@
+// tslint:disable:no-duplicate-imports
import { unlink } from 'fs';
import { join } from 'path';
import { promisify } from 'util';
import { AnchorType } from '../src/api/draw';
import { TextBody, TextDocument } from '../src/api/office';
import { Color, FontPitch, HorizontalAlignment, PageBreak, ParagraphStyle, TabStop } from '../src/api/style';
-import { TabStopType, TextTransformation, Typeface } from '../src/api/style';
+import { TabStopType, TextTransformation, Typeface, VerticalAlignment } from '../src/api/style';
const FILEPATH = './integration.fodt';
@@ -65,14 +66,22 @@ xdescribe('integration', () => {
heading.setStyle(style);
});
- it('keep with next', () => {
+ it('background color', () => {
const style = new ParagraphStyle();
- style.setKeepWithNext();
+ style.setBackgroundColor(Color.fromRgb(0, 255, 0));
- const heading = body.addParagraph('Keep together with next paragraph');
+ const heading = body.addParagraph('Some text with green colored background');
heading.setStyle(style);
});
+ it('align text', () => {
+ const style = new ParagraphStyle();
+ style.setHorizontalAlignment(HorizontalAlignment.Center);
+
+ const paragraph = body.addParagraph('Some centered text');
+ paragraph.setStyle(style);
+ });
+
it('keep together', () => {
const style = new ParagraphStyle();
style.setKeepTogether();
@@ -81,14 +90,54 @@ xdescribe('integration', () => {
heading.setStyle(style);
});
- it('align text', () => {
+ it('keep with next', () => {
const style = new ParagraphStyle();
- style.setHorizontalAlignment(HorizontalAlignment.Center);
+ style.setKeepWithNext();
+
+ const heading = body.addParagraph('Keep together with next paragraph');
+ heading.setStyle(style);
+ });
+
+ it('line height', () => {
+ const style = new ParagraphStyle();
+ style.setLineHeight('120%');
+
+ const heading = body.addParagraph('Some text with 120% line height');
+ heading.setStyle(style);
+ });
+
+ it('line height at least', () => {
+ const style = new ParagraphStyle();
+ style.setLineHeightAtLeast(40);
+
+ const heading = body.addParagraph('Some text with minimum line height of 40 mm');
+ heading.setStyle(style);
+ });
+
+ it('orphans', () => {
+ const style = new ParagraphStyle();
+ style.setOrphans(2);
+
+ const heading = body.addParagraph('Break paragraph after 2 lines of text at the earliest');
+ heading.setStyle(style);
+ });
+
+ it('vertical align text', () => {
+ const style = new ParagraphStyle();
+ style.setVerticalAlignment(VerticalAlignment.Middle);
const paragraph = body.addParagraph('Some centered text');
paragraph.setStyle(style);
});
+ it('widows', () => {
+ const style = new ParagraphStyle();
+ style.setWidows(2);
+
+ const heading = body.addParagraph('Write at least 2 lines of text after a break of the paragraph');
+ heading.setStyle(style);
+ });
+
it('tab stops', () => {
const style = new ParagraphStyle();
style.addTabStop(new TabStop(40));