Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions lib/elements/link.ts
Original file line number Diff line number Diff line change
@@ -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) {
Comment thread
MalcolmCusack marked this conversation as resolved.
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<RGB>, // 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
Comment thread
MalcolmCusack marked this conversation as resolved.
}

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)));
}
}
}
5 changes: 3 additions & 2 deletions lib/fonts.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
export const Fonts = {
ARIAL: "Arial",
COMIC_SANS: "Comic Sans MS",
CALIBRI: "Calibri",
COMIC_SANS: "Comic Sans MS",
GEORGIA: "Georgia",
IMPACT: "Impact",
TAHOMA: "Tahoma",
HELVETICA: "Helvetica",
VERDANA: "Verdana",
COURIER_NEW: "Courier New",
PALATINO: "Palatino Linotype",
TIMES_NEW_ROMAN: "Times New Roman"
TIMES_NEW_ROMAN: "Times New Roman",
} as const;
2 changes: 2 additions & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
3 changes: 3 additions & 0 deletions lib/rgb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
5 changes: 5 additions & 0 deletions lib/rtf-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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('}', '\\}')
Expand Down
12 changes: 11 additions & 1 deletion lib/rtf.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down