Changes
Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
co-authored by
renee-png
parent
bd9fe62f38
commit
914e717d42
@@ -520,6 +520,65 @@ export function CustomFormBuilder() {
|
||||
|
||||
const LIST_INDENT = 24; // px per nesting level
|
||||
|
||||
const toRoman = (num: number) => {
|
||||
const map: Array<[number, string]> = [
|
||||
[1000, "M"],
|
||||
[900, "CM"],
|
||||
[500, "D"],
|
||||
[400, "CD"],
|
||||
[100, "C"],
|
||||
[90, "XC"],
|
||||
[50, "L"],
|
||||
[40, "XL"],
|
||||
[10, "X"],
|
||||
[9, "IX"],
|
||||
[5, "V"],
|
||||
[4, "IV"],
|
||||
[1, "I"],
|
||||
];
|
||||
let out = "";
|
||||
let n = Math.max(1, Math.floor(num));
|
||||
for (const [value, glyph] of map) {
|
||||
while (n >= value) {
|
||||
out += glyph;
|
||||
n -= value;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
const formatOrderedMarker = (index: number, listEl: HTMLElement) => {
|
||||
const explicitType = (listEl.getAttribute("type") || "").toLowerCase();
|
||||
const styleType = (listEl.style.listStyleType || "").toLowerCase();
|
||||
const kind = explicitType || styleType;
|
||||
if (kind === "a" || kind === "lower-alpha") return `${String.fromCharCode(96 + Math.max(1, index))}.`;
|
||||
if (kind === "A" || kind === "upper-alpha") return `${String.fromCharCode(64 + Math.max(1, index))}.`;
|
||||
if (kind === "i" || kind === "lower-roman") return `${toRoman(index).toLowerCase()}.`;
|
||||
if (kind === "I" || kind === "upper-roman") return `${toRoman(index)}.`;
|
||||
return `${index}.`;
|
||||
};
|
||||
|
||||
const extractLeadingManualMarker = (runs: InlineRun[]) => {
|
||||
const preview = runs.map((r) => r.text).join("").slice(0, 32);
|
||||
const match = preview.match(/^\s*((?:\d+|[A-Za-z]+|[ivxlcdmIVXLCDM]+)[.)])\s+/);
|
||||
if (!match) return { marker: null as string | null, runs };
|
||||
let remaining = match[0].length;
|
||||
const trimmed: InlineRun[] = [];
|
||||
for (const run of runs) {
|
||||
if (remaining <= 0) {
|
||||
trimmed.push(run);
|
||||
continue;
|
||||
}
|
||||
if (run.text.length <= remaining) {
|
||||
remaining -= run.text.length;
|
||||
continue;
|
||||
}
|
||||
trimmed.push({ ...run, text: run.text.slice(remaining) });
|
||||
remaining = 0;
|
||||
}
|
||||
return { marker: match[1], runs: mergeRuns(trimmed) };
|
||||
};
|
||||
|
||||
const pushListItems = (
|
||||
listEl: HTMLElement,
|
||||
ordered: boolean,
|
||||
@@ -528,26 +587,26 @@ export function CustomFormBuilder() {
|
||||
const items = Array.from(listEl.children).filter(
|
||||
(c) => (c as HTMLElement).tagName.toLowerCase() === "li",
|
||||
) as HTMLElement[];
|
||||
let n = 1;
|
||||
const startAttr = parseInt(listEl.getAttribute("start") || "1", 10);
|
||||
let n = Number.isFinite(startAttr) && startAttr > 0 ? startAttr : 1;
|
||||
for (const li of items) {
|
||||
// Collect inline runs from this li, excluding nested lists.
|
||||
const inlineNodes = Array.from(li.childNodes).filter((c) => {
|
||||
if (c.nodeType !== Node.ELEMENT_NODE) return true;
|
||||
const t = (c as HTMLElement).tagName.toLowerCase();
|
||||
return t !== "ol" && t !== "ul";
|
||||
});
|
||||
const runs: InlineRun[] = [];
|
||||
inlineNodes.forEach((n) => runs.push(...collectRuns(n)));
|
||||
inlineNodes.forEach((node) => runs.push(...collectRuns(node)));
|
||||
const merged = mergeRuns(runs);
|
||||
const manual = ordered ? extractLeadingManualMarker(merged) : { marker: null as string | null, runs: merged };
|
||||
out.push({
|
||||
kind: "text",
|
||||
runs: merged,
|
||||
runs: manual.runs,
|
||||
align: "left",
|
||||
indent: depth * LIST_INDENT,
|
||||
marker: ordered ? `${n}.` : "•",
|
||||
marker: ordered ? manual.marker ?? formatOrderedMarker(n, listEl) : "•",
|
||||
});
|
||||
n += 1;
|
||||
// Recurse into any nested lists.
|
||||
Array.from(li.children).forEach((child) => {
|
||||
const tag = (child as HTMLElement).tagName.toLowerCase();
|
||||
if (tag === "ol") pushListItems(child as HTMLElement, true, depth + 1);
|
||||
@@ -801,21 +860,28 @@ export function CustomFormBuilder() {
|
||||
}
|
||||
const indentPx = seg.indent || 0;
|
||||
const indentPt = indentPx * 0.75; // px → pt
|
||||
const segMaxW = maxW - indentPt;
|
||||
const startX = margin + indentPt;
|
||||
const runsForLayout: InlineRun[] = seg.marker
|
||||
? [{ text: `${seg.marker} ` }, ...seg.runs]
|
||||
: seg.runs;
|
||||
const visualLines = runsToLines(runsForLayout);
|
||||
const markerGapPt = seg.marker ? Math.max(14, doc.getTextWidth(seg.marker) + 8) : 0;
|
||||
const markerX = margin + indentPt;
|
||||
const textX = markerX + markerGapPt;
|
||||
const segMaxW = Math.max(1, maxW - indentPt - markerGapPt);
|
||||
const visualLines = runsToLines(seg.runs);
|
||||
if (visualLines.length === 0 || (visualLines.length === 1 && visualLines[0].length === 0)) {
|
||||
y += lineH;
|
||||
} else {
|
||||
if (seg.marker) {
|
||||
if (y > pageH - margin) {
|
||||
doc.addPage();
|
||||
y = margin;
|
||||
}
|
||||
doc.setFont(pdfFont, "normal");
|
||||
doc.text(seg.marker, markerX, y);
|
||||
}
|
||||
for (const lineRuns of visualLines) {
|
||||
if (lineRuns.length === 0) {
|
||||
y += lineH;
|
||||
continue;
|
||||
}
|
||||
y = drawRunsLine(lineRuns, startX, y, segMaxW, seg.align);
|
||||
y = drawRunsLine(lineRuns, textX, y, segMaxW, seg.align);
|
||||
}
|
||||
}
|
||||
y += seg.caption ? 2 : 4;
|
||||
@@ -958,7 +1024,7 @@ export function CustomFormBuilder() {
|
||||
new DocxParagraph({
|
||||
alignment: align,
|
||||
spacing: docxLineSpacing,
|
||||
indent: indentTwips ? { left: indentTwips, hanging: seg.marker ? 360 : undefined } : seg.marker ? { left: 360, hanging: 360 } : undefined,
|
||||
indent: seg.marker ? { left: indentTwips + 360, hanging: 360 } : indentTwips ? { left: indentTwips } : undefined,
|
||||
children: runsWithMarker,
|
||||
}),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user