diff --git a/lib/elements/link.ts b/lib/elements/link.ts new file mode 100644 index 0000000..a6c2754 --- /dev/null +++ b/lib/elements/link.ts @@ -0,0 +1,92 @@ +import { Format } from "../format"; +import { RGB } from "../rgb"; +import { Element } from "./element"; +import { getRTFSafeText } from "../rtf-utils"; + +export class LinkElement extends Element { + url: string; + displayText: string; + isExternalURL: boolean; // Flag to indicate if the URL is external + constructor(url: string, displayText: string, format?: Format, isExternalURL: boolean = false) { + // If no format provided, create a default one for links + const linkFormat = format ?? new Format(); + + // Ensure typical link styling if not overridden + if (!linkFormat.color) { + linkFormat.color = RGB.BLUE; // Default to blue + } + if (!linkFormat.underline) { + linkFormat.underline = true; // Default to underline + } + + super(linkFormat); + this.url = url; + this.displayText = displayText; + this.isExternalURL = isExternalURL; // Store the external URL flag + } + + getRTFCode( + colorTable: Array, // Use the RGB type + fontTable: string[], + callback: (err: Error | null, result?: string) => void + ): void { + try { + // Ensure format properties (color, font) are registered in tables + this.format.updateTables(colorTable, fontTable); + + // Prepare format codes (font, size, color, underline, etc.) + let formatCodes = ""; + if (this.format.fontPos >= 0) { + formatCodes += `\\f${this.format.fontPos}`; + } + if (this.format.fontSize > 0) { + formatCodes += `\\fs${this.format.fontSize * 2}`; + } + // Always add color for the link display text + if (this.format.colorPos >= 0) { + // Add 1 because color table is 1-indexed in RTF (\cf1, \cf2, ...) + formatCodes += `\\cf${this.format.colorPos + 1}`; + } + // Always add underline for the link display text + if (this.format.underline) { + formatCodes += "\\ul"; + } + if (this.format.bold) { + formatCodes += "\\b"; + } + if (this.format.italic) { + formatCodes += "\\i"; + } + // Add other format codes as needed (strike, super, sub) + + // Escape the URL and display text for safety within RTF structure + // Note: URLs themselves might have issues if they contain complex chars, + // but basic escaping handles common cases like spaces or backslashes. + const safeUrl = getRTFSafeText(this.url); + const safeDisplayText = getRTFSafeText(this.displayText); + + // Construct the RTF string for the hyperlink field + // Structure: {\pard \[format codes] {\field{\*\fldinst HYPERLINK "URL"}{\fldrslt [DISPLAY_TEXT]}}}[\format resets \pard] + // The outer {} and \pard ensures the formatting is scoped to the link. + // We apply format codes *before* the \field block. + // \fldrslt block inherits the preceding format. + let rtfString = `{\\pard ${formatCodes} ` // Start group, apply formatting + + if (this.isExternalURL) { + // If it's an external URL, we need to add the external link format + rtfString += `{\\field{\\*\\fldinst HYPERLINK "${safeUrl}"}}{\\fldrslt ${safeDisplayText}}`; + } else { + rtfString += `{\\field{\\*\\fldinst HYPERLINK A}{\\fldrslt ${safeUrl}}}` // The field itself + } + + rtfString += `\\ul0\\b0\\i0}`; // Reset specific styles and end group. \\pard might be needed depending on context. + + // Add the closing braces for the RTF structure (if needed) + //rtfString += `\\pard}`; // End the paragraph and reset formatting + + callback(null, rtfString); + } catch (err) { + callback(err instanceof Error ? err : new Error(String(err))); + } + } +} \ No newline at end of file diff --git a/lib/fonts.ts b/lib/fonts.ts index f6f42d6..b411ef3 100644 --- a/lib/fonts.ts +++ b/lib/fonts.ts @@ -1,6 +1,7 @@ export const Fonts = { ARIAL: "Arial", - COMIC_SANS: "Comic Sans MS", + CALIBRI: "Calibri", + COMIC_SANS: "Comic Sans MS", GEORGIA: "Georgia", IMPACT: "Impact", TAHOMA: "Tahoma", @@ -8,5 +9,5 @@ export const Fonts = { VERDANA: "Verdana", COURIER_NEW: "Courier New", PALATINO: "Palatino Linotype", - TIMES_NEW_ROMAN: "Times New Roman" + TIMES_NEW_ROMAN: "Times New Roman", } as const; diff --git a/lib/index.ts b/lib/index.ts index 8c6c8a0..a78cb83 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -17,3 +17,5 @@ export * from './elements/group'; export * from './elements/image'; export * from './elements/table'; export * from './elements/text'; +export * from './elements/link'; +export * from './elements/command'; diff --git a/lib/rgb.ts b/lib/rgb.ts index f802246..a479b25 100644 --- a/lib/rgb.ts +++ b/lib/rgb.ts @@ -8,4 +8,7 @@ export class RGB { this.green = green; this.blue = blue; } + + // Add a static constant for convenience + static readonly BLUE = new RGB(0, 0, 255); } diff --git a/lib/rtf-utils.ts b/lib/rtf-utils.ts index d9de5c8..2538a0f 100644 --- a/lib/rtf-utils.ts +++ b/lib/rtf-utils.ts @@ -10,6 +10,11 @@ export function getRTFSafeText(text: any): string { if (typeof text === "object" && text.hasOwnProperty("safe") && !text.safe) { return text.text; } + + if (typeof text !== 'string') { + return ''; + } + return text.replaceAll('\\', '\\\\') .replaceAll('{', '\\{') .replaceAll('}', '\\}') diff --git a/lib/rtf.ts b/lib/rtf.ts index 2afe018..135ac9f 100644 --- a/lib/rtf.ts +++ b/lib/rtf.ts @@ -1,4 +1,3 @@ -import { RGB } from "./rgb"; import { Element } from "./elements/element"; // adjust the types as needed import { Format } from "./format"; import * as Utils from "./rtf-utils"; @@ -8,6 +7,7 @@ import { TextElement } from "./elements/text"; import { GroupElement } from "./elements/group"; import async from "async"; import { CommandElement } from "./elements/command"; +import { LinkElement } from "./elements/link"; export class RTF { pageNumbering: boolean; @@ -52,6 +52,16 @@ export class RTF { } } + writeLink(url: string, displayText: string, format?: Format, groupName?: string, isExternalURL?: boolean) { + const element = new LinkElement(url, displayText, format, isExternalURL); + const groupIndex = this._groupIndex(groupName); + if (groupName !== undefined && groupIndex >= 0) { + (this.elements[groupIndex] as GroupElement).addElement(element); + } else { + this.elements.push(element); + } + } + addTable(table: any) { this.elements.push(table); } diff --git a/package-lock.json b/package-lock.json index 43bc469..fe6c0cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@happy-doc/rtf", - "version": "0.1.2", + "version": "0.1.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@happy-doc/rtf", - "version": "0.1.2", + "version": "0.1.3", "license": "MIT", "dependencies": { "async": "^0.9.2", diff --git a/package.json b/package.json index fb4320d..752de08 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@happy-doc/rtf", - "version": "0.1.2", + "version": "0.1.3", "description": "Assists with creating rich text documents.", "main": "dist/index.js", "types": "dist/index.d.ts",