<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>UDF Document Viewer</title>
    <style>
        body {
            margin: 0;
            padding: 20px;
            font-family: Arial, sans-serif;
            background: #f0f0f0;
        }

        .file-controls {
            margin: 20px auto;
            max-width: 800px;
        }

        .file-controls input[type="file"] {
            width: 100%;
            padding: 10px;
            margin-bottom: 10px;
            border: 1px solid #ddd;
            border-radius: 4px;
        }

        .dropzone {
            width: 100%;
            height: 100px;
            border: 2px dashed #ccc;
            border-radius: 4px;
            text-align: center;
            padding: 20px;
            margin-bottom: 20px;
            background: #f9f9f9;
        }

        .dropzone.dragover {
            background-color: #e1e1e1;
            border-color: #999;
        }

        .page {
            width: 794px;
            /* ~21 cm @ 96dpi  */
            height: 1122px;
            /* ~29.7 cm @ 96dpi */
            margin: 20px auto;
            background: white;
            position: relative;
            box-sizing: border-box;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);

            /* optional: keep flex so you can place header/content/footer in order */
            display: flex;
            flex-direction: column;

            /* If you want to enforce no extra growth, do overflow: hidden. 
     Then .page won't expand beyond 1122px. */
            overflow: hidden;
        }

        .page-header {
            /* normal or absolutely positioned—your choice */
            border-bottom: 1px solid #eee;
        }

        .page-content {
            /* fill the remaining vertical space if using flex */
            flex: 1;
            /* you can also add internal margins/padding, etc. */
            padding: 25px;
        }

        .page-footer {
            border-top: 1px solid #eee;
            /* or flex: 0 if you want a fixed size, etc. */
        }

        /* Remove the dashed border, 2cm padding, large margin-top in .page+.page */
        .page+.page {
            margin-top: 20px;
            /* just a small gap or zero */
        }

        /* Also remove page-break-after, break-after: page, min-height, etc. */


        .page:last-child {
            page-break-after: avoid;
            break-after: avoid;
        }

        @media print {
            .page {
                margin: 0;
                box-shadow: none;
                page-break-after: always;
                break-after: page;
                border: none;
                padding: 0;
            }

            .page+.page {
                margin-top: 0;
                border: none;
                padding: 0;
            }

            .page-header {
                position: fixed;
                top: 0;
                left: 0;
                right: 0;
            }

            .page-footer {
                position: fixed;
                bottom: 0;
                left: 0;
                right: 0;
            }

            .page:last-child {
                page-break-after: avoid;
                break-after: avoid;
            }
        }

        table {
            border-collapse: collapse;
            table-layout: fixed;
            width: 100%;
            margin-bottom: 1em;
        }

        .bordered-table {
            border: 1px solid #000;
            width: 100%;
            table-layout: fixed;
        }

        .bordered-table td {
            border: 1px solid #000;
            word-wrap: break-word;
            overflow-wrap: break-word;
        }

        td {
            position: relative;
            overflow: hidden;
            padding: 8px;
            vertical-align: top;
        }

        .row-type-header td {
            font-weight: bold;
            background-color: #f5f5f5;
        }

        .row-type-footer td {
            font-style: italic;
            background-color: #f9f9f9;
        }

        @media print {
            table {
                page-break-inside: avoid;
            }

            tr {
                page-break-inside: avoid;
            }

            td {
                page-break-inside: avoid;
            }
        }

        img {
            max-width: 100%;
            height: auto;
            vertical-align: middle;
        }

        [contenteditable] img {
            cursor: pointer;
        }

        .content-spacing {
            margin-right: 0.01em;
        }

        p {
            margin: 0;
            padding: 0;
            min-height: 1em;
            word-wrap: break-word;
            overflow-wrap: break-word;
            position: relative;
        }

        /* Ensure proper content alignment */
        p>span {
            vertical-align: baseline;
        }

        br {
            display: block;
            content: "";
            margin-top: 0;
            line-height: inherit;
        }

        .content-space {
            white-space: pre;
            display: inline-block;
        }

        p:empty::after {
            content: '\00a0';
            white-space: pre;
        }

        p+p {
            margin-top: 0;
            /* Reset browser default spacing */
        }

        .image-container {
            display: block;
            position: relative;
            margin: 0;
            line-height: 0;
        }

        .image-container img {
            display: inline-block;
            vertical-align: middle;
        }

        /* Remove max-width constraint if causing issues */
        .udf-image {
            height: auto;
            /* Only set auto height if you want to maintain aspect ratio */
        }

        .image-container.error {
            border: 1px dashed #ff0000;
            padding: 20px;
            text-align: center;
            background-color: #fff5f5;
        }

        .image-error {
            color: #ff0000;
            font-size: 14px;
            line-height: 1.4;
        }

        .image-error-overlay {
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-color: rgba(255, 0, 0, 0.1);
            display: flex;
            align-items: center;
            justify-content: center;
            color: #ff0000;
        }

        .image-loading-overlay {
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-color: rgba(255, 255, 255, 0.8);
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .error-icon {
            font-size: 24px;
            margin-right: 8px;
        }

        @media print {
            .image-container {
                break-inside: avoid;
            }
        }

        .content-container {
            display: inline-block;
            position: relative;
            vertical-align: baseline;
        }

        .content-text {
            display: inline-block;
            position: relative;
            white-space: pre-wrap;
            word-wrap: break-word;
            overflow-wrap: break-word;
        }

        /* Add to your CSS */
        .content-text+.content-text {
            margin-left: 0.1em;
            /* Small gap between content pieces */
        }

        /* Add spacing between content elements */
        .content-container+.content-container {
            margin-left: 0.1em;
            /* Default minimal spacing */
        }

        /* Handle vertical alignment */
        .content-container[data-valign="middle"] {
            vertical-align: middle;
        }

        .content-container[data-valign="bottom"] {
            vertical-align: bottom;
        }

        .content-container[data-valign="top"] {
            vertical-align: top;
        }

        /* Add to your existing CSS */
        .barcode-container {
            display: block;
            margin: 10px 0;
            page-break-inside: avoid;
        }

        .barcode-container svg {
            display: inline-block;
            max-width: 100%;
            height: auto;
            vertical-align: middle;
        }

        /* For justified alignment */
        .barcode-container[data-alignment="3"] {
            text-align: justify;
            width: 100%;
        }

        /* For centered alignment */
        .barcode-container[data-alignment="1"] {
            text-align: center;
            width: 100%;
        }

        /* For right alignment */
        .barcode-container[data-alignment="2"] {
            text-align: right;
            width: 100%;
        }

        @media print {
            .barcode-container {
                page-break-inside: avoid;
                break-inside: avoid;
            }
        }
    </style>
</head>

<body>
    <div class="file-controls">
        <input type="file" id="fileInput" accept=".udf">
        <div class="dropzone" id="dropzone">
            Drag & drop UDF file here or click above to select
        </div>
    </div>
    <div id="document"></div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.0/dist/JsBarcode.all.min.js"></script>
    <script>
        class UDFViewer {
            // (Add inside the UDFViewer class)
            getMaxPageHeightInPx(pageFormat) {
                // 1) Convert margins from “points” to pixels
                //    (42.525 points ~ 56.7px at 1.3333 ratio)
                const topMarginPx = this.convertPointsToPx(pageFormat.topMargin);
                const bottomMarginPx = this.convertPointsToPx(pageFormat.bottomMargin);

                // 2) Figure out base paper height in centimeters.
                //    For A4 portrait, 29.7cm tall; if orientation=2 => swap => 21.0cm tall.
                let heightCm = 29.7;  // default A4 portrait
                let widthCm = 21.0;

                // If your XML sets mediaSizeName=2 => "A4 Landscape"
                // or 3 => "Letter", etc., handle accordingly:
                if (pageFormat.mediaSizeName === '2') {
                    // e.g. "A4 Landscape"
                    heightCm = 21.0;
                    widthCm = 29.7;
                } else if (pageFormat.mediaSizeName === '3') {
                    // e.g. "Letter" (landscape?), 25.4 x 20.32
                    heightCm = 20.32;
                    widthCm = 25.4;
                } else if (pageFormat.mediaSizeName === '4') {
                    // e.g. "Letter Portrait"
                    heightCm = 25.4;
                    widthCm = 20.32;
                }

                // Also respect paperOrientation='2' => landscape (swap them)
                if (pageFormat.paperOrientation === '2') {
                    const temp = heightCm;
                    heightCm = widthCm;
                    widthCm = temp;
                }

                // 3) Convert cm -> px at ~96 dpi
                const totalPageHeightPx = heightCm * (96.0 / 2.54);

                // 4) Subtract top + bottom margins
                const availableHeightPx = totalPageHeightPx - (topMarginPx + bottomMarginPx);

                // To avoid tiny rounding issues, subtract a small “buffer”
                const safeHeight = availableHeightPx - 10;
                return safeHeight;
            }

            static PAGE_SIZES = {
                '1': { width: '21cm', height: '29.7cm', name: 'A4' },
                '2': { width: '29.7cm', height: '21cm', name: 'A4 Landscape' },
                '3': { width: '25.4cm', height: '20.32cm', name: 'Letter' },
                '4': { width: '20.32cm', height: '25.4cm', name: 'Letter Portrait' },
                // Add more page sizes as needed
            };

            static ORIENTATION = {
                '1': 'portrait',
                '2': 'landscape'
            };
            constructor(container) {
                this.container = container;
                this.rawContent = '';
                this.styles = {};
                this.currentElements = null;
                this.listCounters = {};

            }

            loadUDF(xmlContent) {
                try {
                    const parser = new DOMParser();
                    const xmlDoc = parser.parseFromString(xmlContent, "text/xml");

                    // Clear previous content
                    this.container.innerHTML = '';

                    // Get CDATA content and clean it
                    const contentElement = xmlDoc.querySelector('content');
                    this.rawContent = contentElement ? contentElement.textContent : '';
                    if (this.rawContent.startsWith('<![CDATA[') && this.rawContent.endsWith(']]>')) {
                        this.rawContent = this.rawContent.substring(9, this.rawContent.length - 3);
                    }

                    // Parse styles
                    const stylesElement = xmlDoc.querySelector('styles');
                    if (stylesElement) {
                        this.parseStyles(stylesElement);
                    }

                    // Create and configure first page
                    const pageFormat = this.parsePageFormat(xmlDoc);
                    this.currentPageFormat = pageFormat; // Store page format for subsequent pages

                    // Don't create the first page here - let parseElements handle it
                    const elements = xmlDoc.querySelector('elements');
                    if (elements) {
                        this.currentElements = elements;

                        // Create first page
                        const { page, content } = this.createPage(pageFormat);
                        this.container.appendChild(page);

                        // Parse elements into the content container
                        this.parseElements(elements, content);
                        this.updatePageNumbers();
                    }
                } catch (error) {
                    console.error('UDF loading error:', error);
                    throw error;
                }
            }

            async parseElements(elements, container) {
                let currentPageContent = container;
                let currentPageDiv = container.parentNode;

                // maxPageHeightPx = how tall the content area can be before we do a new page
                const maxPageHeightPx = this.getMaxPageHeightInPx(this.currentPageFormat);

                for (const element of Array.from(elements.children)) {
                    try {
                        // Check for <page-break> or skip header/footer, etc., same as before
                        if (element.tagName.toLowerCase() === 'page-break') {
                            const { page, content } = this.createPage(this.currentPageFormat);
                            this.container.appendChild(page);
                            currentPageDiv = page;
                            currentPageContent = content;
                            continue;
                        }

                        if (element.tagName.toLowerCase() === 'header' ||
                            element.tagName.toLowerCase() === 'footer') {
                            console.log('Skipping header/footer element');
                            continue;
                        }

                        // Build new element (paragraph / table / etc.)
                        let newElement;
                        switch (element.tagName.toLowerCase()) {
                            case 'paragraph':
                                console.log('Creating paragraph element');
                                newElement = this.createParagraph(element);
                                break;
                            case 'table':
                                console.log('Creating table element');
                                newElement = this.createTable(element);
                                break;
                            default:
                                console.warn('Unknown element type:', element.tagName.toLowerCase());
                                continue;
                        }
                        if (!newElement) continue;

                        // (A) Serialize the original XML into a string (optional if it's huge)
                        const serialized = new XMLSerializer().serializeToString(element);

                        // (B) Make the comment node
                        const commentNode = document.createComment(
                            `Original XML for <${element.tagName}>: ${serialized}`
                        );

                        // (C) Append the comment to the container
                        currentPageContent.appendChild(commentNode);

                        // Append to current page-content
                        currentPageContent.appendChild(newElement);
                        console.log('Added new element to current page');

                        // Place a hidden sentinel to measure the used space in .page-content
                        const sentinel = document.createElement('div');
                        sentinel.style.cssText = 'visibility:hidden; height:0; margin:0; padding:0;';
                        currentPageContent.appendChild(sentinel);

                        // measure how far down sentinel is => the "used height"
                        const contentBottom = sentinel.offsetTop + sentinel.offsetHeight;
                        currentPageContent.removeChild(sentinel);

                        console.log('Checking if', contentBottom, '>', maxPageHeightPx);

                        if (contentBottom > maxPageHeightPx) {
                            // overflow -> remove the element from this page
                            currentPageContent.removeChild(newElement);

                            // create a new page
                            const { page: newPageDiv, content: newPageContent } = this.createPage(this.currentPageFormat);
                            this.container.appendChild(newPageDiv);

                            // append the element on the new page
                            newPageContent.appendChild(newElement);

                            // update references
                            currentPageDiv = newPageDiv;
                            currentPageContent = newPageContent;
                        }

                    } catch (error) {
                        console.error('Error processing element:', error);
                    }
                }

                console.log('Finished parseElements');
            }


            parseStyles(stylesElement) {
                if (!stylesElement) return;

                this.styles = {};

                // Get all style elements
                const styleElements = Array.from(stylesElement.getElementsByTagName('style'));

                // First pass: handle default style
                const defaultStyle = styleElements.find(style => style.getAttribute('name') === 'default');
                if (defaultStyle) {
                    this.styles['default'] = this.createStyleObject(defaultStyle);
                }

                // Second pass: handle other styles
                styleElements.forEach(style => {
                    const styleName = style.getAttribute('name');
                    if (styleName && styleName !== 'default') {
                        this.styles[styleName] = this.createStyleObject(style);
                    }
                });

                // Ensure hvl-default exists (it's used as fallback in your XML)
                if (!this.styles['hvl-default']) {
                    this.styles['hvl-default'] = {
                        name: 'hvl-default',
                        family: 'Times New Roman',
                        size: 12,
                        description: 'Gövde',
                        bold: false,
                        italic: false,
                        foreground: this.parseColor(-13421773)
                    };
                }
            }

            parseFontAttribute(fontAttribute) {
                if (!fontAttribute) return null;

                try {
                    // Parse the font attribute string
                    const matches = {
                        family: fontAttribute.match(/family=(.*?)[,\]]/),
                        name: fontAttribute.match(/name=(.*?)[,\]]/),
                        style: fontAttribute.match(/style=(.*?)[,\]]/),
                        size: fontAttribute.match(/size=(\d+)/)
                    };

                    return {
                        family: matches.family ? matches.family[1] : null,
                        name: matches.name ? matches.name[1] : null,
                        style: matches.style ? matches.style[1] : null,
                        size: matches.size ? parseInt(matches.size[1]) : null
                    };
                } catch (error) {
                    console.warn('Error parsing font attribute:', error);
                    return null;
                }
            }

            createStyleObject(styleElement) {
                // Parse the font attribute first
                const fontAttr = this.parseFontAttribute(styleElement.getAttribute('FONT_ATTRIBUTE_KEY'));

                // Get direct attributes with fallbacks
                const directAttrs = {
                    name: styleElement.getAttribute('name'),
                    description: styleElement.getAttribute('description') || '',
                    family: styleElement.getAttribute('family'),
                    size: styleElement.getAttribute('size'),
                    bold: styleElement.getAttribute('bold'),
                    italic: styleElement.getAttribute('italic'),
                    foreground: styleElement.getAttribute('foreground')
                };

                // Create the style object with proper fallbacks
                return {
                    name: directAttrs.name,
                    description: directAttrs.description,

                    // Use cascading fallbacks for font properties
                    family: directAttrs.family || fontAttr?.family || 'Times New Roman',
                    size: parseInt(directAttrs.size) || fontAttr?.size || 12,

                    // Parse boolean values
                    bold: directAttrs.bold === 'true' || (fontAttr?.style?.includes('bold') ?? false),
                    italic: directAttrs.italic === 'true' || (fontAttr?.style?.includes('italic') ?? false),

                    // Handle foreground color
                    foreground: directAttrs.foreground ?
                        this.parseColor(parseInt(directAttrs.foreground)) :
                        this.parseColor(-13421773),

                    // Store original font attribute
                    fontAttribute: fontAttr,

                    // Enhanced validation
                    isValid() {
                        return this.name &&
                            this.family &&
                            !isNaN(this.size) &&
                            this.size > 0;
                    },

                    // Add method to get computed style
                    getComputedStyle() {
                        return {
                            fontFamily: this.family,
                            fontSize: `${this.size}pt`,
                            fontWeight: this.bold ? 'bold' : 'normal',
                            fontStyle: this.italic ? 'italic' : 'normal',
                            color: this.foreground
                        };
                    }
                };
            }

            // Add these new helper methods after parseStyles
            getStyleForElement(element) {
                // Get style name from element or parent elements
                const styleName = element.getAttribute('style') || 'hvl-default';
                return this.styles[styleName] || this.styles['hvl-default'] || this.createDefaultStyle();
            }

            createDefaultStyle() {
                return {
                    family: 'Times New Roman',
                    size: 12,
                    bold: false,
                    italic: false,
                    foreground: this.parseColor(-13421773)
                };
            }

            applyStylesToElement(element, styleObj) {
                if (!element || !element.style || !styleObj) return;

                if (styleObj.family) element.style.fontFamily = styleObj.family;
                if (styleObj.size) element.style.fontSize = styleObj.size + 'pt';
                if (styleObj.bold) element.style.fontWeight = 'bold';
                if (styleObj.italic) element.style.fontStyle = 'italic';
                if (styleObj.foreground) element.style.color = styleObj.foreground;
            }

            parsePageFormat(xmlDoc) {
                const pageFormat = xmlDoc.querySelector('pageFormat');
                if (!pageFormat) return this.getDefaultPageFormat();

                return {
                    mediaSizeName: pageFormat.getAttribute('mediaSizeName') || '1',
                    leftMargin: parseFloat(pageFormat.getAttribute('leftMargin') || '42.525'),
                    rightMargin: parseFloat(pageFormat.getAttribute('rightMargin') || '42.525'),
                    topMargin: parseFloat(pageFormat.getAttribute('topMargin') || '42.525'),
                    bottomMargin: parseFloat(pageFormat.getAttribute('bottomMargin') || '42.525'),
                    paperOrientation: pageFormat.getAttribute('paperOrientation') || '1',
                    headerFOffset: parseFloat(pageFormat.getAttribute('headerFOffset') || '20.0'),
                    footerFOffset: parseFloat(pageFormat.getAttribute('footerFOffset') || '20.0')
                };
            }

            getDefaultPageFormat() {
                return {
                    mediaSizeName: '1',
                    leftMargin: 42.525,
                    rightMargin: 42.525,
                    topMargin: 42.525,
                    bottomMargin: 42.525,
                    paperOrientation: '1',
                    headerFOffset: 20.0,
                    footerFOffset: 20.0
                };
            }

            createPage(pageFormat) {
                // Make the .page wrapper
                const page = document.createElement('div');
                page.className = 'page';

                if (pageFormat.leftMargin) {
                    page.style.paddingLeft = this.convertHorizontalMargin(pageFormat.leftMargin) + 'px';
                }
                if (pageFormat.rightMargin) {
                    page.style.paddingRight = this.convertHorizontalMargin(pageFormat.rightMargin) + 'px';
                }
                if (pageFormat.topMargin) {
                    page.style.paddingTop = this.convertPointsToPx(pageFormat.topMargin) + 'px';
                }
                if (pageFormat.bottomMargin) {
                    page.style.paddingBottom = this.convertPointsToPx(pageFormat.bottomMargin) + 'px';
                }

                if (pageFormat.paperOrientation === "2") {
                    // e.g. A4 landscape 29.7cm x 21cm
                    page.style.width = '29.7cm';
                } else {
                    // A4 portrait 21cm x 29.7cm
                    page.style.width = '21cm';
                }
                // do NOT do => page.style.minHeight = '29.7cm';

                // Create the .page-content (flex:1 child)
                const content = document.createElement('div');
                content.className = 'page-content';

                const header = this.createHeaderContainer(pageFormat);
                if (header) page.appendChild(header);

                // This child will expand in flex, but won't have a forced height
                page.appendChild(content);

                const footer = this.createFooterContainer(pageFormat);
                if (footer) page.appendChild(footer);

                // Return
                return { page, content };
            }


            createHeaderContainer(pageFormat) {
    const headerElement = this.currentElements?.querySelector('header');
    if (!headerElement) return null;

    const header = document.createElement('div');
    header.className = 'page-header';

    const headerStartNumStr = headerElement.getAttribute('pageNumber-pageStartNumStr') || '1';
    header.setAttribute('data-page-start-num', headerStartNumStr);

    header.style.height = this.convertPointsToPx(pageFormat.headerFOffset) + 'px';

    // Handle background/foreground for the entire header
    const bgAttr = headerElement.getAttribute('background');
    if (bgAttr) {
        header.style.backgroundColor = this.parseColor(parseInt(bgAttr));
    }
    const fgAttr = headerElement.getAttribute('foreground');
    if (fgAttr) {
        header.style.color = this.parseColor(parseInt(fgAttr));
    }

    // Read the pageNumber-spec, color, fontFace, fontSize
    const pageNumberSpec = headerElement.getAttribute('pageNumber-spec');
    const pageNumberColor = headerElement.getAttribute('pageNumber-color');
    const pageNumberFontFace = headerElement.getAttribute('pageNumber-fontFace');  // <--
    const pageNumberFontSize = headerElement.getAttribute('pageNumber-fontSize');  // <--
    const pageNumberForeStr  = headerElement.getAttribute('pageNumber-foreStr');

    // Parse child paragraphs
    Array.from(headerElement.children).forEach(child => {
        if (child.tagName.toLowerCase() === 'paragraph') {
            const p = this.createParagraph(child);

            // If we see pageNumberSpec, we apply the pageNumber-face/size
            if (pageNumberSpec) {
                // If pageNumberFontFace is e.g. "Arial", apply it:
                if (pageNumberFontFace) {
                    p.style.fontFamily = pageNumberFontFace;
                }
                // If pageNumberFontSize is "11", apply it (convert to pt):
                if (pageNumberFontSize) {
                    p.style.fontSize = pageNumberFontSize + 'pt';
                }

                // If pageNumberColor is e.g. "-16777216", parse it:
                if (pageNumberColor) {
                    p.style.color = this.parseColor(parseInt(pageNumberColor));
                }

                // If pageNumberForeStr => inject "prefix {PAGE}" or something
                if (pageNumberForeStr) {
                    p.innerHTML = pageNumberForeStr + ' {PAGE}'; 
                    // so updatePageNumbers() will replace {PAGE}
                }

                p.style.textAlign = 'center'
            }

            header.appendChild(p);
        }
    });

    return header;
}


createFooterContainer(pageFormat) {
    const footerElement = this.currentElements?.querySelector('footer');
    if (!footerElement) return null;

    const footer = document.createElement('div');
    footer.className = 'page-footer';

    const footerStartNumStr = footerElement.getAttribute('pageNumber-pageStartNumStr') || '0';
    footer.setAttribute('data-page-start-num', footerStartNumStr);

    footer.style.height = this.convertPointsToPx(pageFormat.footerFOffset) + 'px';

    // background/foreground
    const bgAttr = footerElement.getAttribute('background');
    if (bgAttr) {
        footer.style.backgroundColor = this.parseColor(parseInt(bgAttr));
    }
    const fgAttr = footerElement.getAttribute('foreground');
    if (fgAttr) {
        footer.style.color = this.parseColor(parseInt(fgAttr));
    }

    // read pageNumber-spec, color, face, size
    const pageNumberSpec = footerElement.getAttribute('pageNumber-spec');
    const pageNumberColor = footerElement.getAttribute('pageNumber-color');
    const pageNumberFontFace = footerElement.getAttribute('pageNumber-fontFace');
    const pageNumberFontSize = footerElement.getAttribute('pageNumber-fontSize');
    const pageNumberForeStr  = footerElement.getAttribute('pageNumber-foreStr');

    // parse child paragraphs
    Array.from(footerElement.children).forEach(child => {
        if (child.tagName.toLowerCase() === 'paragraph') {
            const p = this.createParagraph(child);

            if (pageNumberSpec) {
                if (pageNumberFontFace) {
                    p.style.fontFamily = pageNumberFontFace;
                }
                if (pageNumberFontSize) {
                    p.style.fontSize = pageNumberFontSize + 'pt';
                }
                if (pageNumberColor) {
                    p.style.color = this.parseColor(parseInt(pageNumberColor));
                }

                if (pageNumberForeStr) {
                    p.innerHTML = pageNumberForeStr + ' {PAGE}';
                }

                p.style.textAlign = 'right'
            }

            footer.appendChild(p);
        }
    });

    return footer;
}

            createParagraph(paragraphElement) {
                const p = document.createElement('p');

                // (1) Collect paragraph attributes
                const attributes = this.parseParagraphAttributes(paragraphElement);
                const baseStyle = this.getStyleForElement(paragraphElement);

                // (2) **Handle bullet/number** prefix if present
                //     Only do this if Bulleted="true" or Numbered="true"
                const isBulleted = paragraphElement.getAttribute('Bulleted') === 'true';
                const isNumbered = paragraphElement.getAttribute('Numbered') === 'true';

                if (isBulleted || isNumbered) {
                    // bulleted => convertHorizontalMargin
                    attributes.leftIndent = this.convertHorizontalMargin(attributes.leftIndent);
                } else {
                    // normal => convertPointsToPx
                    attributes.leftIndent = this.convertPointsToPx(attributes.leftIndent);
                }

                this.applyParagraphStyles(p, attributes, baseStyle);

                if (isBulleted) {
                    // get bullet type (e.g. "BULLET_TYPE_ELLIPSE")
                    const bulletType = paragraphElement.getAttribute('BulletType');
                    let bulletChar = '•'; // default

                    if (bulletType === 'BULLET_TYPE_ELLIPSE') {
                        bulletChar = '•'; // or '◦' or any symbol you prefer
                    }
                    // if you have other bullet types, handle them here

                    // Make a span for bullet + a bit of spacing
                    const bulletSpan = document.createElement('span');
                    bulletSpan.textContent = bulletChar + ' ';
                    bulletSpan.style.marginRight = '5px';

                    // Indent deeper levels if "ListLevel" is set
                    const listLevel = parseInt(paragraphElement.getAttribute('ListLevel') || '1', 10);
                    bulletSpan.style.marginLeft = (listLevel - 1) * 20 + 'px';

                    p.appendChild(bulletSpan);
                }
                else if (isNumbered) {
                    // read "ListId" so we can keep separate counters
                    const listId = paragraphElement.getAttribute('ListId') || 'default';
                    if (!this.listCounters[listId]) {
                        this.listCounters[listId] = 1;
                    }
                    const currentNumber = this.listCounters[listId];

                    // read "NumberType" if needed
                    // e.g. "NUMBER_TYPE_NUMBER_DOT" => "1."
                    const numberType = paragraphElement.getAttribute('NumberType');
                    let prefix = currentNumber + '.'; // default

                    if (numberType === 'NUMBER_TYPE_NUMBER_DOT') {
                        prefix = currentNumber + '.';
                    }
                    // you might handle other formats if you like

                    // create a span
                    const numSpan = document.createElement('span');
                    numSpan.textContent = prefix + ' ';
                    numSpan.style.marginRight = '5px';

                    // Indent deeper levels if "ListLevel" is set
                    const listLevel = parseInt(paragraphElement.getAttribute('ListLevel') || '1', 10);
                    numSpan.style.marginLeft = (listLevel - 1) * 20 + 'px';

                    p.appendChild(numSpan);

                    // increment the counter for next time
                    this.listCounters[listId]++;
                }

                // Process content with improved handling
                let lastOffset = -1;
                let lastWasBreak = false;

                for (const child of Array.from(paragraphElement.children)) {
                    try {
                        switch (child.tagName.toLowerCase()) {
                            case 'content':
                                const startOffset = parseInt(child.getAttribute('startOffset'));
                                const length = parseInt(child.getAttribute('length'));

                                // Handle gaps in content
                                if (lastOffset !== -1 && startOffset > lastOffset + 1) {
                                    const gapText = this.rawContent.substring(lastOffset + 1, startOffset);
                                    if (gapText.includes('\n') || gapText.includes('\r')) {
                                        if (!lastWasBreak) {
                                            p.appendChild(document.createElement('br'));
                                            lastWasBreak = true;
                                        }
                                    }
                                }

                                // Get the actual content text
                                const contentText = this.rawContent.substr(startOffset, length);

                                // Check for line breaks within the content itself
                                if (contentText.includes('\n') || contentText.includes('\r')) {
                                    // Split the content at line breaks
                                    const segments = contentText.split(/[\n\r]+/);
                                    segments.forEach((segment, index) => {
                                        if (index > 0 && !lastWasBreak) {
                                            p.appendChild(document.createElement('br'));
                                            lastWasBreak = true;
                                        }
                                        if (segment.length > 0) {
                                            const segmentSpan = document.createElement('span');
                                            segmentSpan.textContent = segment;
                                            // Copy styles from original content element
                                            this.applyStylesToElement(segmentSpan, this.getStyleForElement(child));
                                            p.appendChild(segmentSpan);
                                            lastWasBreak = false;
                                        }
                                    });
                                } else {
                                    // Handle content without line breaks as before
                                    const span = this.createContent(child);
                                    if (span) {
                                        p.appendChild(span);
                                        lastWasBreak = false;
                                    }
                                }

                                lastOffset = startOffset + length - 1;
                                break;

                            case 'break':
                            case 'br':
                                p.appendChild(document.createElement('br'));
                                lastWasBreak = true;
                                break;

                            case 'space':
                                p.appendChild(document.createTextNode('\u00A0'));
                                lastWasBreak = false;
                                break;

                            case 'image':
                                const img = this.createImage(child);
                                if (img instanceof Node) {
                                    p.appendChild(img);
                                    lastWasBreak = false;
                                }
                                break;

                            case 'barcode':
                                const barcodeEl = this.createBarcode(child);
                                if (barcodeEl) {
                                    p.appendChild(barcodeEl);
                                }
                                break;

                            default:
                                console.warn('Unknown paragraph child element:', child.tagName.toLowerCase());
                        }
                    } catch (error) {
                        console.error('Error processing paragraph child:', error);
                    }
                }

                // Handle trailing line breaks
                if (lastOffset !== -1) {
                    const remainingText = this.rawContent.substring(lastOffset + 1);
                    const trailingBreaks = (remainingText.match(/\n/g) || []).length;

                    for (let i = 0; i < trailingBreaks && i < 2; i++) { // Limit to max 2 consecutive breaks
                        p.appendChild(document.createElement('br'));
                    }
                }

                return p;
            }

            createBarcode(barcodeElement) {
                try {
                    // Get parent paragraph alignment if it exists
                    const parentParagraph = barcodeElement.closest('paragraph');
                    const alignment = parentParagraph ? parseInt(parentParagraph.getAttribute('Alignment')) : 0;

                    // Create container with proper alignment
                    const container = document.createElement('div');
                    container.className = 'barcode-container';

                    // Handle paragraph alignment
                    switch (alignment) {
                        case 1: // Center
                            container.style.textAlign = 'center';
                            container.style.width = '100%';
                            break;
                        case 2: // Right
                            container.style.textAlign = 'right';
                            container.style.width = '100%';
                            break;
                        case 3: // Justified
                            container.style.textAlign = 'justify';
                            container.style.width = '100%';
                            break;
                        default: // Left
                            container.style.textAlign = 'left';
                    }

                    // Get barcode data and validate
                    const barcodeData = barcodeElement.getAttribute('barcodeData');
                    if (!barcodeData) {
                        throw new Error('No barcode data provided');
                    }

                    // Create SVG element
                    const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");

                    // Default options with better sizing
                    const options = {
                        format: "CODE128",
                        width: 2,           // Bar width
                        height: 50,         // Bar height
                        margin: 10,         // Quiet zone around barcode
                        displayValue: true,  // Show text below barcode
                        fontSize: 12,
                        font: "Arial",
                        textAlign: "center",
                        textPosition: "bottom",
                        textMargin: 5,
                        background: "#ffffff"
                    };

                    // Get any size-related attributes
                    const width = parseFloat(barcodeElement.getAttribute('width'));
                    const height = parseFloat(barcodeElement.getAttribute('height'));
                    const scale = parseFloat(barcodeElement.getAttribute('scale') || '1.0');

                    // Apply size if specified
                    if (!isNaN(width)) {
                        options.width = this.convertPointsToPx(width);
                    }
                    if (!isNaN(height)) {
                        options.height = this.convertPointsToPx(height);
                    }

                    // Generate barcode
                    try {
                        JsBarcode(svg, barcodeData, options);

                        // Apply scale if specified
                        if (scale !== 1.0) {
                            svg.style.transform = `scale(${scale})`;
                            svg.style.transformOrigin = 'left top';
                        }

                        // Make SVG responsive
                        svg.setAttribute('width', '100%');
                        svg.style.maxWidth = options.width * barcodeData.length + (2 * options.margin) + 'px';

                    } catch (barcodeError) {
                        console.error('Barcode generation error:', barcodeError);
                        container.innerHTML = `[Barcode: ${barcodeData}]`;
                        container.classList.add('barcode-error');
                        return container;
                    }

                    // Add SVG to container
                    container.appendChild(svg);

                    // Add spacing for document flow
                    container.style.margin = '10px 0';
                    container.style.pageBreakInside = 'avoid';

                    return container;

                } catch (error) {
                    console.error('Barcode creation error:', error);
                    const fallback = document.createElement('div');
                    fallback.className = 'barcode-error';
                    fallback.textContent = '[Barcode Error]';
                    return fallback;
                }
            }


            parseParagraphAttributes(paragraphElement) {
                return {
                    leftIndent: parseFloat(paragraphElement.getAttribute('LeftIndent') || '0'),
                    rightIndent: parseFloat(paragraphElement.getAttribute('RightIndent') || '0'),
                    firstLineIndent: parseFloat(paragraphElement.getAttribute('FirstLineIndent') || '0'),
                    lineSpacing: parseFloat(paragraphElement.getAttribute('LineSpacing') || '0'),
                    lineSpacingType: parseInt(paragraphElement.getAttribute('LineSpacingType') || '0'),
                    spacingBefore: parseFloat(paragraphElement.getAttribute('SpacingBefore') || '0'),
                    spacingAfter: parseFloat(paragraphElement.getAttribute('SpacingAfter') || '0'),
                    alignment: parseInt(paragraphElement.getAttribute('Alignment') || '0'),
                    style: paragraphElement.getAttribute('style') || 'hvl-default',
                    indent: parseFloat(paragraphElement.getAttribute('Indent') || '0'),
                    hanging: parseFloat(paragraphElement.getAttribute('Hanging') || '0'),
                    spacingBetween: parseFloat(paragraphElement.getAttribute('SpacingBetween') || '0')
                };
            }

            calculateLineSpacing(spacing, type) {
                // Line spacing types:
                // 0 = Single
                // 1 = One and Half
                // 2 = Double
                // 3 = Exact
                // 4 = Multiple
                switch (type) {
                    case 0: return '1.0';
                    case 1: return '1.5';
                    case 2: return '2.0';
                    case 3: return `${this.convertPointsToPx(spacing)}px`;
                    case 4: return spacing.toString();
                    default: return '1.0';
                }
            }

            applyParagraphStyles(paragraph, attributes, baseStyle) {
                // Apply base style first
                this.applyStylesToElement(paragraph, baseStyle);

                // Apply indentation
                paragraph.style.paddingLeft = attributes.leftIndent + 'px';

                if (attributes.rightIndent) {
                    paragraph.style.marginRight = this.convertHorizontalMargin(attributes.rightIndent) + 'px';
                }

                if (attributes.firstLineIndent) {
                    paragraph.style.textIndent = this.convertPointsToPx(attributes.firstLineIndent) + 'px';
                }

                // Apply spacing
                if (attributes.spacingBefore) {
                    paragraph.style.marginTop = this.convertPointsToPx(attributes.spacingBefore) + 'px';
                }

                if (attributes.spacingAfter) {
                    paragraph.style.marginBottom = this.convertPointsToPx(attributes.spacingAfter) + 'px';
                }

                // Apply line spacing
                paragraph.style.lineHeight = this.calculateLineSpacing(attributes.lineSpacing, attributes.lineSpacingType);

                // Apply alignment
                switch (attributes.alignment) {
                    case 0: paragraph.style.textAlign = 'left'; break;
                    case 1: paragraph.style.textAlign = 'center'; break;
                    case 2: paragraph.style.textAlign = 'right'; break;
                    case 3: paragraph.style.textAlign = 'justify'; break;
                    default: paragraph.style.textAlign = 'left';
                }
            }

            // Helper method to determine if line break is needed
            shouldAddLineBreak(currentElement, allElements, currentIndex) {
                // Check if this is a content element
                if (currentElement.tagName.toLowerCase() !== 'content') {
                    return false;
                }

                const startOffset = parseInt(currentElement.getAttribute('startOffset'));
                const length = parseInt(currentElement.getAttribute('length'));

                // If there's a next element, check if there's a gap in the content
                if (currentIndex < allElements.length - 1) {
                    const nextElement = allElements[currentIndex + 1];
                    if (nextElement.tagName.toLowerCase() === 'content') {
                        const nextOffset = parseInt(nextElement.getAttribute('startOffset'));
                        // If there's a gap between current content end and next content start
                        if (nextOffset > (startOffset + length + 1)) {
                            return true;
                        }
                    }
                }

                // Check if content ends with newline character
                const content = this.rawContent.substr(startOffset, length);
                return content.endsWith('\n') || content.endsWith('\r');
            }

            createContent(contentElement) {
                const span = document.createElement('span');

                try {
                    const startOffset = parseInt(contentElement.getAttribute('startOffset'));
                    const length = parseInt(contentElement.getAttribute('length'));

                    if (!isNaN(startOffset) && !isNaN(length)) {
                        const rawText = this.rawContent.substr(startOffset, length);
                        // Use innerHTML instead of textContent to handle line breaks
                        span.innerHTML = this.processWhitespace(rawText);
                    }

                    // Get base style from parent or default
                    const baseStyle = this.getStyleForElement(contentElement);
                    this.applyStylesToElement(span, baseStyle);

                    // Apply inline styles
                    if (contentElement.getAttribute('bold') === 'true') span.style.fontWeight = 'bold';
                    if (contentElement.getAttribute('italic') === 'true') span.style.fontStyle = 'italic';
                    if (contentElement.getAttribute('underline') === 'true') span.style.textDecoration = 'underline';
                    if (contentElement.getAttribute('subscript') === 'true') span.style.verticalAlign = 'sub';
                    if (contentElement.getAttribute('superscript') === 'true') span.style.verticalAlign = 'super';
                    if (contentElement.getAttribute('strikethrough') === 'true') span.style.textDecoration = 'line-through';

                    // Handle explicit colors
                    const background = contentElement.getAttribute('background');
                    if (background) {
                        span.style.backgroundColor = this.parseColor(parseInt(background));
                    }

                    const foreground = contentElement.getAttribute('foreground');
                    if (foreground) {
                        span.style.color = this.parseColor(parseInt(foreground));
                    }

                    // Handle explicit font properties
                    const family = contentElement.getAttribute('family');
                    if (family) span.style.fontFamily = family;

                    const size = contentElement.getAttribute('size');
                    if (size) span.style.fontSize = size + 'pt';

                    if (contentElement.getAttribute('subscript') === 'true') {
                        span.style.verticalAlign = 'sub';
                    }

                } catch (error) {
                    console.error('Content processing error:', error);
                }

                return span;
            }

            parseImageAttributes(imageElement) {
                if (!imageElement || !imageElement.getAttribute) {
                    return null;
                }

                return {
                    width: parseFloat(imageElement.getAttribute('width') || '0'),
                    height: parseFloat(imageElement.getAttribute('height') || '0'),
                    scale: parseFloat(imageElement.getAttribute('scale') || '1'),
                    alignment: parseInt(imageElement.getAttribute('alignment') || '0'),
                    vOffset: parseFloat(imageElement.getAttribute('vOffset') || '0'),
                    hOffset: parseFloat(imageElement.getAttribute('hOffset') || '0'),
                    keepAspectRatio: imageElement.getAttribute('keepAspectRatio') !== 'false',
                    imageData: imageElement.getAttribute('imageData') || '',
                    altText: imageElement.getAttribute('altText') || '',
                    rotation: parseInt(imageElement.getAttribute('rotation') || '0')
                };
            }

            createImage(imageElement) {
                const container = document.createElement('div');
                container.className = 'image-container';

                try {
                    const attrs = this.parseImageAttributes(imageElement);
                    if (!attrs) {
                        throw new Error('Invalid image element');
                    }

                    const img = document.createElement('img');
                    img.className = 'udf-image';
                    img.alt = attrs.altText || 'Document image';

                    if (attrs.imageData) {
                        const cleanData = attrs.imageData.replace(/[\r\n\s]/g, '');
                        img.src = 'data:image/png;base64,' + cleanData;
                    } else {
                        throw new Error('No image data provided');
                    }

                    // Set dimensions immediately
                    if (attrs.width > 0) {
                        img.style.width = this.convertPointsToPx(attrs.width) + 'px';
                    }
                    if (attrs.height > 0) {
                        img.style.height = this.convertPointsToPx(attrs.height) + 'px';
                    }

                    // Handle alignment
                    switch (attrs.alignment) {
                        case 1: // Center
                            container.style.textAlign = 'center';
                            break;
                        case 2: // Right
                            container.style.textAlign = 'right';
                            break;
                        default: // Left
                            container.style.textAlign = 'left';
                    }

                    // Handle scale
                    if (attrs.scale !== 1) {
                        img.style.transform = `scale(${attrs.scale})`;
                        img.style.transformOrigin = 'left top';
                    }

                    // Handle offsets
                    if (attrs.vOffset) {
                        container.style.marginTop = this.convertPointsToPx(attrs.vOffset) + 'px';
                    }
                    if (attrs.hOffset) {
                        container.style.marginLeft = this.convertHorizontalMargin(attrs.hOffset) + 'px';
                    }

                    container.appendChild(img);

                    // Error handling
                    img.onerror = () => {
                        container.innerHTML = '[Image loading failed]';
                        container.style.color = 'red';
                        container.style.padding = '10px';
                        container.style.border = '1px solid red';
                    };

                } catch (error) {
                    console.error('Image processing error:', error);
                    container.textContent = '[Image processing error]';
                    container.style.color = 'red';
                }

                return container;
            }

            applyImageStyles(img, container, attrs) {
                // Container styles
                container.style.display = 'block';
                container.style.position = 'relative';
                container.style.margin = '0';
                container.style.maxWidth = '100%';

                // Handle alignment
                switch (attrs.alignment) {
                    case 1: // Center
                        container.style.textAlign = 'center';
                        container.style.margin = '0 auto';
                        break;
                    case 2: // Right
                        container.style.textAlign = 'right';
                        container.style.marginLeft = 'auto';
                        break;
                    case 0: // Left
                    default:
                        container.style.textAlign = 'left';
                        break;
                }

                // Basic image styles
                img.style.display = 'inline-block';
                img.style.maxWidth = '100%';
                img.style.verticalAlign = 'middle';

                // Handle dimensions
                if (attrs.width > 0 || attrs.height > 0) {
                    if (attrs.keepAspectRatio) {
                        // Maintain aspect ratio while setting max dimensions
                        if (attrs.width > 0) img.style.maxWidth = this.convertPointsToPx(attrs.width) + 'px';
                        if (attrs.height > 0) img.style.maxHeight = this.convertPointsToPx(attrs.height) + 'px';
                        img.style.width = 'auto';
                        img.style.height = 'auto';
                    } else {
                        // Force exact dimensions
                        if (attrs.width > 0) img.style.width = this.convertPointsToPx(attrs.width) + 'px';
                        if (attrs.height > 0) img.style.height = this.convertPointsToPx(attrs.height) + 'px';
                    }
                }

                // Apply scale
                if (attrs.scale !== 1) {
                    img.style.transform = `scale(${attrs.scale})`;
                    img.style.transformOrigin = 'left top';
                }

                // Apply rotation
                if (attrs.rotation) {
                    const currentTransform = img.style.transform || '';
                    img.style.transform = `${currentTransform} rotate(${attrs.rotation}deg)`;
                }

                // Handle offsets
                if (attrs.vOffset) container.style.marginTop = this.convertPointsToPx(attrs.vOffset) + 'px';
                if (attrs.hOffset) container.style.marginLeft = this.convertHorizontalMargin(attrs.hOffset) + 'px';
            }

            createTable(tableElement) {
                const tableAttrs = this.parseTableAttributes(tableElement);
                const table = document.createElement('table');

                // Apply table styles
                table.style.borderCollapse = 'collapse'; // Keep this to ensure borders merge properly
                table.style.width = tableAttrs.width ? this.convertPointsToPx(tableAttrs.width) + 'px' : '100%';

                // Apply table alignment
                switch (tableAttrs.alignment) {
                    case 1: table.style.margin = '0 auto'; break;
                    case 2: table.style.margin = '0 0 0 auto'; break;
                    default: table.style.margin = '0';
                }

                // Apply border if specified - this is the key change
                const hasBorder = tableAttrs.border === 'borderCell';
                if (hasBorder) {
                    table.classList.add('bordered-table');
                }

                // Create colgroup for column widths
                if (tableAttrs.columnSpans.length > 0) {
                    const colgroup = document.createElement('colgroup');
                    tableAttrs.columnSpans.forEach(width => {
                        const col = document.createElement('col');
                        if (width > 0) {
                            col.style.width = this.convertPointsToPx(width) + 'px';
                        }
                        colgroup.appendChild(col);
                    });
                    table.appendChild(colgroup);
                }

                // Process rows
                Array.from(tableElement.getElementsByTagName('row')).forEach(row => {
                    table.appendChild(this.createTableRow(row, tableAttrs));
                });

                return table;
            }

            createTableRow(rowElement, tableAttrs) {
                const rowAttrs = this.parseRowAttributes(rowElement);
                const tr = document.createElement('tr');

                // Apply row styles
                if (rowAttrs.height > 0) {
                    tr.style.height = this.convertPointsToPx(rowAttrs.height) + 'px';
                }
                if (rowAttrs.minHeight > 0) {
                    tr.style.minHeight = this.convertPointsToPx(rowAttrs.minHeight) + 'px';
                }
                if (rowAttrs.background) {
                    tr.style.backgroundColor = this.parseColor(parseInt(rowAttrs.background));
                }

                // Add row type as class for potential styling
                if (rowAttrs.rowType) {
                    tr.classList.add(`row-type-${rowAttrs.rowType}`);
                }

                // Get row-specific column spans and cells
                const rowColumnSpans = rowElement.getAttribute('columnSpans');
                const cells = Array.from(rowElement.getElementsByTagName('cell'));

                // If there's only one cell and rowColumnSpans is "100", make it span all columns
                if (cells.length === 1 && rowColumnSpans === "100") {
                    const td = this.createTableCell(cells[0], tableAttrs);
                    td.colSpan = tableAttrs.columnCount;
                    tr.appendChild(td);
                } else {
                    // Process cells normally (existing code)
                    cells.forEach(cell => {
                        tr.appendChild(this.createTableCell(cell, tableAttrs));
                    });
                }

                return tr;
            }

            createTableCell(cellElement, tableAttrs) {
                const cellAttrs = this.parseCellAttributes(cellElement);
                const td = document.createElement('td');

                // Apply cell attributes
                if (cellAttrs.colSpan > 1) td.colSpan = cellAttrs.colSpan;
                if (cellAttrs.rowSpan > 1) td.rowSpan = cellAttrs.rowSpan;

                // Apply cell styles
                td.style.padding = '8px';

                // Apply vertical alignment
                switch (cellAttrs.verticalAlignment) {
                    case 1: td.style.verticalAlign = 'middle'; break;
                    case 2: td.style.verticalAlign = 'bottom'; break;
                    default: td.style.verticalAlign = 'top';
                }

                // Apply background color
                if (cellAttrs.background) {
                    td.style.backgroundColor = this.parseColor(parseInt(cellAttrs.background));
                }

                // Add cell padding from attributes
                if (tableAttrs.cellPadding) {
                    td.style.padding = this.convertPointsToPx(tableAttrs.cellPadding) + 'px';
                }

                // Process cell content
                const paragraphs = cellElement.getElementsByTagName('paragraph');
                Array.from(paragraphs).forEach(para => {
                    // Get indents from paragraph
                    const leftIndent = parseFloat(para.getAttribute('LeftIndent') || '0');
                    const rightIndent = parseFloat(para.getAttribute('RightIndent') || '0');

                    const p = this.createParagraph(para);
                    p.style.margin = '0'; // Reset paragraph margins inside cells
                    p.style.whiteSpace = 'pre-wrap'; // Ensure whitespace is preserved in cells

                    // Apply indents to paragraph instead of cell
                    if (leftIndent > 0) {
                        p.style.paddingLeft = this.convertHorizontalMargin(leftIndent) + 'px';
                    }
                    if (rightIndent > 0) {
                        p.style.paddingRight = this.convertHorizontalMargin(rightIndent) + 'px';
                    }

                    td.appendChild(p);
                });

                return td;
            }

            parseTableAttributes(tableElement) {
                return {
                    tableName: tableElement.getAttribute('tableName'),
                    columnCount: parseInt(tableElement.getAttribute('columnCount') || '1'),
                    columnSpans: (tableElement.getAttribute('columnSpans') || '').split(',').map(Number),
                    border: tableElement.getAttribute('border'),
                    width: tableElement.getAttribute('width'),
                    alignment: parseInt(tableElement.getAttribute('alignment') || '0'),
                    cellSpacing: parseFloat(tableElement.getAttribute('cellSpacing') || '0'),
                    cellPadding: parseFloat(tableElement.getAttribute('cellPadding') || '1')
                };
            }

            parseRowAttributes(rowElement) {
                return {
                    rowName: rowElement.getAttribute('rowName'),
                    rowType: rowElement.getAttribute('rowType'),
                    height: parseFloat(rowElement.getAttribute('height') || '0'),
                    minHeight: parseFloat(rowElement.getAttribute('minHeight') || '0'),
                    background: rowElement.getAttribute('background')
                };
            }

            parseCellAttributes(cellElement) {
                return {
                    colSpan: parseInt(cellElement.getAttribute('colSpan') || '1'),
                    rowSpan: parseInt(cellElement.getAttribute('rowSpan') || '1'),
                    verticalAlignment: parseInt(cellElement.getAttribute('verticalAlignment') || '0'),
                    background: cellElement.getAttribute('background'),
                    borderTop: cellElement.getAttribute('borderTop'),
                    borderBottom: cellElement.getAttribute('borderBottom'),
                    borderLeft: cellElement.getAttribute('borderLeft'),
                    borderRight: cellElement.getAttribute('borderRight')
                };
            }

            getBorderStyle(borderAttribute) {
                if (!borderAttribute) return '';
                // Parse border types: none, single, double, etc.
                const borderTypes = {
                    'none': 'none',
                    'single': '1px solid #000',
                    'double': '3px double #000',
                    'dashed': '1px dashed #000',
                    'dotted': '1px dotted #000'
                };
                return borderTypes[borderAttribute] || '1px solid #000';
            }

            parseColor(colorInt) {
                if (colorInt < 0) {
                    colorInt += Math.pow(2, 32);
                }
                const r = (colorInt >> 16) & 0xFF;
                const g = (colorInt >> 8) & 0xFF;
                const b = colorInt & 0xFF;
                return `rgb(${r},${g},${b})`;
            }

            convertPointsToPx(points) {
                return points ? parseFloat(points) * 1.33333 : 0;
            }

            convertHorizontalMargin(points) {
                return points ? parseFloat(points) * 0.7 : 0;
            }

            // Convert cm to px at 96 DPI
            cmToPx(cm) {
                return cm * (96.0 / 2.54);
            }

            processWhitespace(text) {
                if (!text) return text;

                // Preserve multiple spaces
                text = text.replace(/ {2,}/g, match => '\u00A0'.repeat(match.length));

                // Handle special characters (only need this once)
                text = text.replace(/[¸]/g, match => {
                    switch (match) {
                        case '¸': return '\u00B8'; // cedilla
                        default: return match;
                    }
                });

                // Preserve tabs
                text = text.replace(/\t/g, '\u00A0\u00A0\u00A0\u00A0');

                // Handle line breaks and preserve leading spaces
                text = text.replace(/\r\n|\r|\n/g, '<br>');
                text = text.replace(/^ /gm, '\u00A0');  // Preserve leading spaces

                return text;
            }

            calculateContentPosition(element) {
                return {
                    leftOffset: parseFloat(element.getAttribute('leftOffset') || '0'),
                    topOffset: parseFloat(element.getAttribute('topOffset') || '0'),
                    rightOffset: parseFloat(element.getAttribute('rightOffset') || '0'),
                    bottomOffset: parseFloat(element.getAttribute('bottomOffset') || '0'),
                    verticalAlignment: parseInt(element.getAttribute('verticalAlignment') || '0'),
                    horizontalAlignment: parseInt(element.getAttribute('horizontalAlignment') || '0')
                };
            }

            applyContentPosition(element, position) {
                if (!element || !position) return;

                // Apply offsets
                if (position.leftOffset) {
                    element.style.marginLeft = this.convertHorizontalMargin(position.leftOffset) + 'px';
                }
                if (position.topOffset) {
                    element.style.marginTop = this.convertPointsToPx(position.topOffset) + 'px';
                }
                if (position.rightOffset) {
                    element.style.marginRight = this.convertHorizontalMargin(position.rightOffset) + 'px';
                }
                if (position.bottomOffset) {
                    element.style.marginBottom = this.convertPointsToPx(position.bottomOffset) + 'px';
                }

                // Apply vertical alignment
                switch (position.verticalAlignment) {
                    case 1: // Middle
                        element.style.verticalAlign = 'middle';
                        break;
                    case 2: // Bottom
                        element.style.verticalAlign = 'bottom';
                        break;
                    default: // Top
                        element.style.verticalAlign = 'top';
                }

                // Apply horizontal alignment if not already set by paragraph
                if (!element.style.textAlign) {
                    switch (position.horizontalAlignment) {
                        case 1: // Center
                            element.style.textAlign = 'center';
                            break;
                        case 2: // Right
                            element.style.textAlign = 'right';
                            break;
                        default: // Left
                            element.style.textAlign = 'left';
                    }
                }
            }

            updatePageNumbers() {
                const pages = this.container.querySelectorAll('.page');
                pages.forEach((page, index) => {
                    const header = page.querySelector('.page-header');
                    const footer = page.querySelector('.page-footer');

                    if (header) {
      // parse the offset
      const offsetStr = header.getAttribute('data-page-start-num') || '1';
      const offset = parseInt(offsetStr, 10) || 1; // default to 1 if missing
      // final page number
      const headerPageNum = index + offset;
      this.updatePageNumberInElement(header, headerPageNum);
    }

    // -- FOOTER offset --
    if (footer) {
      // parse the offset
      const offsetStr = footer.getAttribute('data-page-start-num') || '0';
      const offset = parseInt(offsetStr, 10) || 0; // default to 0 if missing
      // final page number
      const footerPageNum = index + offset;
      this.updatePageNumberInElement(footer, footerPageNum);
    }
                });
            }

            updatePageNumberInElement(element, pageNumber) {
                const placeholder = element.innerHTML.replace(
                    /\{PAGE\}/g,
                    pageNumber.toString()
                );
                element.innerHTML = placeholder;
            }

        }

        class UDFLoader {
            constructor(viewer) {
                this.viewer = viewer;
                this.setupFileHandlers();
            }

            setupFileHandlers() {
                const fileInput = document.getElementById('fileInput');
                const dropzone = document.getElementById('dropzone');

                // File input handler
                fileInput.addEventListener('change', (e) => {
                    const file = e.target.files[0];
                    if (file && file.name.toLowerCase().endsWith('.udf')) {
                        this.loadUDFFile(file);
                    } else {
                        alert('Please select a valid UDF file.');
                    }
                });

                // Drag and drop handlers
                dropzone.addEventListener('dragover', (e) => {
                    e.preventDefault();
                    dropzone.classList.add('dragover');
                });

                dropzone.addEventListener('dragleave', (e) => {
                    e.preventDefault();
                    dropzone.classList.remove('dragover');
                });

                dropzone.addEventListener('drop', (e) => {
                    e.preventDefault();
                    dropzone.classList.remove('dragover');
                    const file = e.dataTransfer.files[0];
                    if (file && file.name.toLowerCase().endsWith('.udf')) {
                        this.loadUDFFile(file);
                    } else {
                        alert('Please select a valid UDF file.');
                    }
                });
            }

            async loadUDFFile(file) {
                try {
                    const arrayBuffer = await this.readFileAsArrayBuffer(file);
                    const zip = new JSZip();
                    const zipContent = await zip.loadAsync(arrayBuffer);

                    const contentXml = await zipContent.file("content.xml").async("text");
                    this.viewer.loadUDF(contentXml);
                } catch (error) {
                    console.error('File loading error:', error);
                    alert('Error loading file: ' + error.message);
                }
            }

            readFileAsArrayBuffer(file) {
                return new Promise((resolve, reject) => {
                    const reader = new FileReader();
                    reader.onload = () => resolve(reader.result);
                    reader.onerror = () => reject(reader.error);
                    reader.readAsArrayBuffer(file);
                });
            }
        }

        // Initialize the viewer and loader
        document.addEventListener('DOMContentLoaded', () => {
            const viewer = new UDFViewer(document.getElementById('document'));
            const loader = new UDFLoader(viewer);
        });
    </script>
</body>

</html>