Skip to content

Commit 0aced85

Browse files
authored
feat(style): implement text overline (#545)
Text can be styled to show an overlining including the line style as well as its color and width. Note: not all combinations are supported by readers like LibreOffice.
1 parent 52598c2 commit 0aced85

File tree

8 files changed

+237
-1
lines changed

8 files changed

+237
-1
lines changed

src/api/office/AutomaticStyles.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,16 @@ export class AutomaticStyles implements IStyles {
249249
hash.update(textProperties.getFontVariant());
250250
hash.update(textProperties.getTextTransformation());
251251
hash.update(textProperties.getTypeface().toString());
252+
253+
const overline = textProperties.getOverline();
254+
if (overline) {
255+
hash.update('overline-color' + overline.color);
256+
hash.update('overline-mode' + overline.mode);
257+
hash.update('overline-style' + overline.style);
258+
hash.update('overline-type' + overline.type);
259+
hash.update('overline-width' + overline.width);
260+
}
261+
252262
const underline = textProperties.getUnderline();
253263
if (underline) {
254264
hash.update('underline-color' + underline.color);

src/api/style/ITextProperties.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,27 @@ export interface ITextProperties {
105105
*/
106106
getFontVariant(): FontVariant;
107107

108+
/**
109+
* @since 2.2.0
110+
*/
111+
setOverline(
112+
color: 'font-color' | Color,
113+
width: LineWidth | number,
114+
style: LineStyle,
115+
type: LineType,
116+
mode: LineMode
117+
): void;
118+
119+
/**
120+
* @since 2.2.0
121+
*/
122+
getOverline(): TextLine | undefined;
123+
124+
/**
125+
* @since 2.2.0
126+
*/
127+
removeOverline(): void;
128+
108129
/**
109130
* Sets the transformation that will be applied to the text.
110131
*

src/api/style/ParagraphStyle.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,31 @@ export class ParagraphStyle
529529
return this.textProperties.getFontVariant();
530530
}
531531

532+
/** @inheritDoc */
533+
public setOverline(
534+
color: 'font-color' | Color,
535+
width: LineWidth | number,
536+
style: LineStyle,
537+
type: LineType,
538+
mode: LineMode
539+
): this {
540+
this.textProperties.setOverline(color, width, style, type, mode);
541+
542+
return this;
543+
}
544+
545+
/** @inheritDoc */
546+
public getOverline(): TextLine | undefined {
547+
return this.textProperties.getOverline();
548+
}
549+
550+
/** @inheritDoc */
551+
public removeOverline(): this {
552+
this.textProperties.removeOverline();
553+
554+
return this;
555+
}
556+
532557
/** @inheritDoc */
533558
public getTextTransformation(): TextTransformation {
534559
return this.textProperties.getTextTransformation();

src/api/style/TextProperties.spec.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,91 @@ describe(TextProperties.name, () => {
9494
});
9595
});
9696

97+
describe('overline', () => {
98+
const testLineColor = Color.fromRgb(1, 2, 3);
99+
const testLineMode = LineMode.SkipWhiteSpace;
100+
const testLineStyle = LineStyle.Wave;
101+
const testLineType = LineType.Double;
102+
const testLineWidth = 13.37;
103+
const expectedLine: Readonly<TextLine> = {
104+
color: testLineColor,
105+
mode: testLineMode,
106+
style: testLineStyle,
107+
type: testLineType,
108+
width: testLineWidth,
109+
};
110+
111+
it('return undefined by default', () => {
112+
// Assert
113+
expect(properties.getOverline()).toBeUndefined();
114+
});
115+
116+
it('return line with defaults if no parameters are passed', () => {
117+
// Arrange
118+
const expectedDefaultLine: TextLine = {
119+
color: 'font-color',
120+
mode: LineMode.Continuous,
121+
style: LineStyle.Solid,
122+
type: LineType.Single,
123+
width: LineWidth.Auto,
124+
};
125+
126+
// Act
127+
properties.setOverline();
128+
129+
// Assert
130+
expect(properties.getOverline()).toEqual(expectedDefaultLine);
131+
});
132+
133+
it('return previously set line', () => {
134+
// Act
135+
properties.setOverline(
136+
testLineColor,
137+
testLineWidth,
138+
testLineStyle,
139+
testLineType,
140+
testLineMode
141+
);
142+
143+
// Assert
144+
expect(properties.getOverline()).toEqual(expectedLine);
145+
});
146+
147+
it('ignore invalid value', () => {
148+
// Arrange
149+
properties.setOverline(
150+
testLineColor,
151+
testLineWidth,
152+
testLineStyle,
153+
testLineType,
154+
testLineMode
155+
);
156+
157+
// Act
158+
properties.setOverline(testLineColor, -0.1);
159+
160+
// Assert
161+
expect(properties.getOverline()).toEqual(expectedLine);
162+
});
163+
164+
it('remove previously set border', () => {
165+
// Arrange
166+
properties.setOverline(
167+
testLineColor,
168+
testLineWidth,
169+
testLineStyle,
170+
testLineType,
171+
testLineMode
172+
);
173+
174+
// Act
175+
properties.removeOverline();
176+
177+
// Assert
178+
expect(properties.getOverline()).toBeUndefined();
179+
});
180+
});
181+
97182
describe('text transformation', () => {
98183
it('return `None` by default', () => {
99184
expect(properties.getTextTransformation()).toBe(TextTransformation.None);

src/api/style/TextProperties.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export class TextProperties implements ITextProperties {
2424
private fontSize: number;
2525
private fontVariant: FontVariant;
2626
// private lineThrough: TextLine | undefined;
27-
// private overline: TextLine | undefined;
27+
private overline: TextLine | undefined;
2828
private transformation: TextTransformation;
2929
private typeface: Typeface;
3030
private underline: TextLine | undefined;
@@ -93,6 +93,35 @@ export class TextProperties implements ITextProperties {
9393
return this.fontVariant;
9494
}
9595

96+
/** @inheritDoc */
97+
public setOverline(
98+
color: 'font-color' | Color = 'font-color',
99+
width: LineWidth | number = LineWidth.Auto,
100+
style: LineStyle = LineStyle.Solid,
101+
type: LineType = LineType.Single,
102+
mode: LineMode = LineMode.Continuous
103+
): void {
104+
if (typeof width !== 'number' || isPositiveLength(width)) {
105+
this.overline = {
106+
color,
107+
mode,
108+
style,
109+
type,
110+
width,
111+
};
112+
}
113+
}
114+
115+
/** @inheritDoc */
116+
public getOverline(): TextLine | undefined {
117+
return this.overline;
118+
}
119+
120+
/** @inheritDoc */
121+
public removeOverline(): void {
122+
this.overline = undefined;
123+
}
124+
96125
/** @inheritDoc */
97126
public setTextTransformation(transformation: TextTransformation): void {
98127
this.transformation = transformation;

src/xml/office/StylesWriter.spec.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
LineStyle,
2020
LineType,
2121
LineMode,
22+
LineWidth,
2223
} from '../../api/style';
2324
import { OdfElementName } from '../OdfElementName';
2425
import { StylesWriter } from './StylesWriter';
@@ -854,6 +855,25 @@ describe(StylesWriter.name, () => {
854855
);
855856
});
856857

858+
it('set overline', () => {
859+
testStyle.setOverline(
860+
'font-color',
861+
LineWidth.Bold,
862+
LineStyle.LongDash,
863+
LineType.Single,
864+
LineMode.Continuous
865+
);
866+
867+
stylesWriter.write(commonStyles, testDocument, testRoot);
868+
const documentAsString = new XMLSerializer().serializeToString(
869+
testDocument
870+
);
871+
872+
expect(documentAsString).toMatch(
873+
/<style:text-properties style:text-overline-type="single" style:text-overline-style="long-dash" style:text-overline-width="bold" style:text-overline-color="font-color" style:text-overline-mode="continuous"\/>/
874+
);
875+
});
876+
857877
it('set underline', () => {
858878
testStyle.setUnderline(
859879
Color.fromRgb(1, 2, 3),

src/xml/office/TextPropertiesVisitor.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,38 @@ export class TextPropertiesVisitor {
106106
);
107107
}
108108

109+
const overline = textProperties.getOverline();
110+
if (overline) {
111+
textPropertiesElement.setAttribute(
112+
'style:text-overline-type',
113+
overline.type
114+
);
115+
116+
textPropertiesElement.setAttribute(
117+
'style:text-overline-style',
118+
overline.style
119+
);
120+
121+
textPropertiesElement.setAttribute(
122+
'style:text-overline-width',
123+
typeof overline.width === 'number'
124+
? `${overline.width}pt`
125+
: overline.width
126+
);
127+
128+
textPropertiesElement.setAttribute(
129+
'style:text-overline-color',
130+
overline.color === 'font-color'
131+
? overline.color
132+
: overline.color.toHex()
133+
);
134+
135+
textPropertiesElement.setAttribute(
136+
'style:text-overline-mode',
137+
overline.mode
138+
);
139+
}
140+
109141
if (
110142
typeface === Typeface.Bold ||
111143
typeface === Typeface.BoldItalic ||

test/integration.spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,20 @@ xdescribe('integration', () => {
308308
paragraph.setStyle(style);
309309
});
310310

311+
it('overline', () => {
312+
const style = new ParagraphStyle();
313+
style.setOverline(
314+
Color.fromRgb(62, 180, 137),
315+
LineWidth.Bold,
316+
LineStyle.Wave,
317+
LineType.Single,
318+
LineMode.Continuous
319+
);
320+
321+
const paragraph = body.addParagraph('Some overlined text');
322+
paragraph.setStyle(style);
323+
});
324+
311325
it('text transformation', () => {
312326
const style = new ParagraphStyle();
313327
style.setTextTransformation(TextTransformation.Uppercase);

0 commit comments

Comments
 (0)