Skip to content

Commit 4416a15

Browse files
committed
chore(text): allow underlined; preserve font style from first paragraph
1 parent 99e5219 commit 4416a15

File tree

4 files changed

+117
-23
lines changed

4 files changed

+117
-23
lines changed

src/helper/html-to-multitext-helper.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,9 @@ export class HtmlToMultiTextHelper {
235235
newStyle.isBold = true;
236236
} else if (tagName === 'em' || tagName === 'i') {
237237
newStyle.isItalics = true;
238-
} else if (tagName === 'span') {
238+
} else if (tagName === 'ins') {
239+
newStyle.isUnderlined = true;
240+
} else if (tagName === 'span') {
239241
this.processSpanStyles(element, newStyle);
240242
}
241243

src/helper/modify-text-helper.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ import { Color, TextStyle } from '../types/modify-types';
22
import ModifyColorHelper from './modify-color-helper';
33
import ModifyXmlHelper from './modify-xml-helper';
44
import { XmlElement } from '../types/xml-types';
5-
import { vd } from './general-helper';
65
import XmlElements from './xml-elements';
7-
import { XmlHelper } from './xml-helper';
86
import { MultiTextParagraph } from '../interfaces/imulti-text';
97
import { MultiTextHelper } from './multitext-helper';
108
import { HtmlToMultiTextHelper } from './html-to-multitext-helper';
@@ -85,6 +83,9 @@ export default class ModifyTextHelper {
8583
if (style.isItalics !== undefined) {
8684
ModifyTextHelper.setItalics(style.isItalics)(element);
8785
}
86+
if (style.isUnderlined !== undefined) {
87+
ModifyTextHelper.setUnderlined(style.isUnderlined)(element);
88+
}
8889
};
8990

9091
/**
@@ -123,4 +124,15 @@ export default class ModifyTextHelper {
123124
(element: XmlElement): void => {
124125
ModifyXmlHelper.booleanAttribute('i', isItalics)(element);
125126
};
127+
128+
/**
129+
* Set underlined attribute on text
130+
*/
131+
static setUnderlined =
132+
(isUnderlined: boolean) =>
133+
(element: XmlElement): void => {
134+
if (isUnderlined) {
135+
element.setAttribute('u', 'sng');
136+
}
137+
};
126138
}

src/helper/multitext-helper.ts

Lines changed: 99 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
1-
import { Color, TextStyle } from '../types/modify-types';
2-
import ModifyColorHelper from './modify-color-helper';
3-
import ModifyXmlHelper from './modify-xml-helper';
1+
import { TextStyle } from '../types/modify-types';
42
import { XmlElement } from '../types/xml-types';
5-
import { vd } from './general-helper';
6-
import XmlElements from './xml-elements';
7-
import { XmlHelper } from './xml-helper';
83
import { MultiTextParagraph } from '../interfaces/imulti-text';
94
import ModifyTextHelper from './modify-text-helper';
105

@@ -22,8 +17,64 @@ export class MultiTextHelper {
2217
*/
2318
run(paragraphs: MultiTextParagraph[]): void {
2419
const txBody = this.getOrCreateTxBody();
20+
const defaultStyle = this.extractDefaultStyle(txBody);
2521
this.clearExistingParagraphs(txBody);
26-
this.createParagraphs(txBody, paragraphs);
22+
this.createParagraphs(txBody, paragraphs, defaultStyle);
23+
}
24+
25+
/**
26+
* Extract default style from existing paragraphs
27+
*/
28+
private extractDefaultStyle(txBody: XmlElement): TextStyle {
29+
const defaultStyle: TextStyle = {};
30+
const existingParagraphs = txBody.getElementsByTagName('a:p');
31+
32+
if (existingParagraphs.length === 0) {
33+
return defaultStyle;
34+
}
35+
36+
// Try to get font size from the first text run
37+
const firstPara = existingParagraphs[0];
38+
const firstRun = firstPara.getElementsByTagName('a:r')[0];
39+
40+
if (firstRun) {
41+
const rPr = firstRun.getElementsByTagName('a:rPr')[0];
42+
if (rPr) {
43+
// Extract font size if it exists
44+
const fontSize = rPr.getAttribute('sz');
45+
if (fontSize) {
46+
defaultStyle.size = parseInt(fontSize);
47+
}
48+
49+
// Extract color if it exists
50+
const solidFill = rPr.getElementsByTagName('a:solidFill')[0];
51+
if (solidFill) {
52+
const srgbClr = solidFill.getElementsByTagName('a:srgbClr')[0];
53+
if (srgbClr) {
54+
const colorValue = srgbClr.getAttribute('val');
55+
if (colorValue) {
56+
defaultStyle.color = {
57+
type: 'srgbClr',
58+
value: colorValue,
59+
};
60+
}
61+
}
62+
}
63+
64+
// Extract bold and italic if they exist
65+
const bold = rPr.getAttribute('b');
66+
if (bold === '1') {
67+
defaultStyle.isBold = true;
68+
}
69+
70+
const italic = rPr.getAttribute('i');
71+
if (italic === '1') {
72+
defaultStyle.isItalics = true;
73+
}
74+
}
75+
}
76+
77+
return defaultStyle;
2778
}
2879

2980
/**
@@ -63,8 +114,12 @@ export class MultiTextHelper {
63114
/**
64115
* Create paragraph elements for each MultiTextParagraph
65116
*/
66-
private createParagraphs(txBody: XmlElement, paragraphs: MultiTextParagraph[]): void {
67-
paragraphs.forEach(para => {
117+
private createParagraphs(
118+
txBody: XmlElement,
119+
paragraphs: MultiTextParagraph[],
120+
defaultStyle: TextStyle = {},
121+
): void {
122+
paragraphs.forEach((para) => {
68123
const p = this.document.createElement('a:p');
69124
txBody.appendChild(p);
70125

@@ -76,13 +131,28 @@ export class MultiTextHelper {
76131
}
77132

78133
if (para.textRuns && para.textRuns.length > 0) {
79-
this.createTextRuns(p, para.textRuns);
134+
this.createTextRuns(p, para.textRuns, defaultStyle);
80135
} else if (para.text !== undefined) {
81-
this.createSingleTextRun(p, para.text, para.style);
136+
// Merge default style with provided style
137+
const mergedStyle = this.mergeStyles(defaultStyle, para.style);
138+
this.createSingleTextRun(p, para.text, mergedStyle);
82139
}
83140
});
84141
}
85142

143+
/**
144+
* Merge default style with provided style
145+
*/
146+
private mergeStyles(
147+
defaultStyle: TextStyle = {},
148+
customStyle: TextStyle = {},
149+
): TextStyle {
150+
return {
151+
...defaultStyle,
152+
...customStyle,
153+
};
154+
}
155+
86156
/**
87157
* Apply paragraph styling properties
88158
*/
@@ -138,7 +208,7 @@ export class MultiTextHelper {
138208
const spcPts = this.document.createElement('a:spcPts');
139209
spcPts.setAttribute(
140210
'val',
141-
String(Math.round(paragraphProps.lineSpacing * 100))
211+
String(Math.round(paragraphProps.lineSpacing * 100)),
142212
); // Convert to 100ths of a point
143213
lnSpc.appendChild(spcPts);
144214
pPr.appendChild(lnSpc);
@@ -150,7 +220,7 @@ export class MultiTextHelper {
150220
const spcPts = this.document.createElement('a:spcPts');
151221
spcPts.setAttribute(
152222
'val',
153-
String(Math.round(paragraphProps.spaceBefore * 100))
223+
String(Math.round(paragraphProps.spaceBefore * 100)),
154224
); // Convert to 100ths of a point
155225
spcBef.appendChild(spcPts);
156226
pPr.appendChild(spcBef);
@@ -162,7 +232,7 @@ export class MultiTextHelper {
162232
const spcPts = this.document.createElement('a:spcPts');
163233
spcPts.setAttribute(
164234
'val',
165-
String(Math.round(paragraphProps.spaceAfter * 100))
235+
String(Math.round(paragraphProps.spaceAfter * 100)),
166236
); // Convert to 100ths of a point
167237
spcAft.appendChild(spcPts);
168238
pPr.appendChild(spcAft);
@@ -172,17 +242,22 @@ export class MultiTextHelper {
172242
/**
173243
* Create text runs for a paragraph
174244
*/
175-
private createTextRuns(p: XmlElement, textRuns: Array<{ text: string; style?: TextStyle }>): void {
176-
textRuns.forEach(run => {
245+
private createTextRuns(
246+
p: XmlElement,
247+
textRuns: Array<{ text: string; style?: TextStyle }>,
248+
defaultStyle: TextStyle = {},
249+
): void {
250+
textRuns.forEach((run) => {
177251
const r = this.document.createElement('a:r');
178252
p.appendChild(r);
179253

180254
const rPr = this.document.createElement('a:rPr');
181255
r.appendChild(rPr);
182256

183-
// Apply text styling if specified
184-
if (run.style) {
185-
ModifyTextHelper.style(run.style)(rPr);
257+
// Apply default styling first, then override with text run specific styling
258+
const mergedStyle = this.mergeStyles(defaultStyle, run.style);
259+
if (mergedStyle) {
260+
ModifyTextHelper.style(mergedStyle)(rPr);
186261
}
187262

188263
this.createTextElement(r, run.text || '');
@@ -192,7 +267,11 @@ export class MultiTextHelper {
192267
/**
193268
* Create a single text run with the given text and style
194269
*/
195-
private createSingleTextRun(p: XmlElement, text: string | number, style?: TextStyle): void {
270+
private createSingleTextRun(
271+
p: XmlElement,
272+
text: string | number,
273+
style?: TextStyle,
274+
): void {
196275
const r = this.document.createElement('a:r');
197276
p.appendChild(r);
198277

src/types/modify-types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export type TextStyle = {
4747
color?: Color;
4848
isBold?: boolean;
4949
isItalics?: boolean;
50+
isUnderlined?: boolean;
5051
};
5152

5253
export type ImageStyle = {

0 commit comments

Comments
 (0)