/*
 * SDK Pullenti Unitext, version 4.33, fabruary 2026. Copyright (c) 2013-2026, Pullenti. All rights reserved. 
 * Non-Commercial Freeware and Commercial Software. 
 * This class is generated using the converter UniSharping (www.unisharping.ru) from Pullenti C# project. 
 * The latest version of the code is available on the site www.pullenti.ru
 */

const RefOutArgWrapper = require("./../../../unisharp/RefOutArgWrapper");
const Stream = require("./../../../unisharp/Stream");
const MemoryStream = require("./../../../unisharp/MemoryStream");

const MiscHelper = require("./../../../util/MiscHelper");
const TiffHelperCCITT_Types = require("./TiffHelperCCITT_Types");
const UnilayPage = require("./../../UnilayPage");
const FileFormat = require("./../../FileFormat");
const UnitextDocument = require("./../../UnitextDocument");

class TiffHelper {
    
    static createDoc(content) {
        let res = UnitextDocument._new47(FileFormat.TIF);
        let mem = new MemoryStream(content); 
        try {
            try {
                let ti = new TiffHelper.Tiff(mem);
                let wi = 0;
                let hei = 0;
                let dpx = 0;
                let dpy = 0;
                let m = false;
                let hasTxt = false;
                if (ti.pagesCount === 1) {
                    let p = UnilayPage._new158(content);
                    let wrapwi159 = new RefOutArgWrapper();
                    let wraphei160 = new RefOutArgWrapper();
                    let wrapdpx161 = new RefOutArgWrapper();
                    let wrapdpy162 = new RefOutArgWrapper();
                    let wrapm163 = new RefOutArgWrapper();
                    let wraphasTxt164 = new RefOutArgWrapper();
                    ti.getPageInfo(0, wrapwi159, wraphei160, wrapdpx161, wrapdpy162, wrapm163, wraphasTxt164);
                    wi = wrapwi159.value;
                    hei = wraphei160.value;
                    dpx = wrapdpx161.value;
                    dpy = wrapdpy162.value;
                    m = wrapm163.value;
                    hasTxt = wraphasTxt164.value;
                    p.width = wi;
                    p.height = hei;
                    res.pages.push(p);
                }
                else 
                    for (let i = 0; i < ti.pagesCount; i++) {
                        let pm = new MemoryStream(); 
                        try {
                            ti.store(i, pm);
                            let p = UnilayPage._new158(pm.toByteArray());
                            let wrapwi166 = new RefOutArgWrapper();
                            let wraphei167 = new RefOutArgWrapper();
                            let wrapdpx168 = new RefOutArgWrapper();
                            let wrapdpy169 = new RefOutArgWrapper();
                            let wrapm170 = new RefOutArgWrapper();
                            let wraphasTxt171 = new RefOutArgWrapper();
                            ti.getPageInfo(0, wrapwi166, wraphei167, wrapdpx168, wrapdpy169, wrapm170, wraphasTxt171);
                            wi = wrapwi166.value;
                            hei = wraphei167.value;
                            dpx = wrapdpx168.value;
                            dpy = wrapdpy169.value;
                            m = wrapm170.value;
                            hasTxt = wraphasTxt171.value;
                            p.width = wi;
                            p.height = hei;
                            res.pages.push(p);
                        }
                        finally {
                            pm.close();
                        }
                    }
            } catch (ex) {
                res.errorMessage = ex.message;
            }
        }
        finally {
            mem.close();
        }
        return res;
    }
}


/**
 * Работа с TIFF-документами
 */
TiffHelper.Tiff = class  {
    
    get isMDI() {
        return this.m_Mdi;
    }
    
    constructor(_stream) {
        this.m_Buf = new Uint8Array(32);
        this.m_Msb = false;
        this.m_Mdi = false;
        this.m_IfdOffset = new Array();
        this.m_Stream = null;
        this.fBuf2 = new Uint8Array(16);
        this.stream = _stream;
    }
    
    get stream() {
        return this.m_Stream;
    }
    set stream(value) {
        if (value === null) 
            return value;
        this.m_Stream = value;
        this.m_IfdOffset.splice(0, this.m_IfdOffset.length);
        this.m_Stream.position = 0;
        this.m_Stream.read(this.m_Buf, 0, 2);
        this.m_Mdi = false;
        this.m_Msb = false;
        if (this.m_Buf[0] === (0x49) && this.m_Buf[1] === (0x49)) 
            this.m_Msb = false;
        else if (this.m_Buf[0] === (0x4D) && this.m_Buf[1] === (0x4D)) 
            this.m_Msb = true;
        else if (this.m_Buf[0] === (0x45) && this.m_Buf[1] === (0x50)) 
            this.m_Mdi = true;
        else {
            this.m_Stream = null;
            return value;
        }
        let i = this.readInt(2);
        while (true) {
            i = this.readInt(4);
            if (i <= 0) 
                break;
            this.m_IfdOffset.push(i);
            this.m_Stream.position = i;
            let cou = this.readInt(2);
            this.m_Stream.position = i + 2 + ((cou * 12));
        }
        return value;
    }
    
    get pagesCount() {
        return this.m_IfdOffset.length;
    }
    
    /**
     * Сохранение страницы в отдельном файле
     * @param pageIndex 
     * @param FileName 
     */
    store(pageIndex, fout) {
        if ((pageIndex < 0) || pageIndex >= this.m_IfdOffset.length) 
            throw new Error("Page index error");
        if (this.m_Msb) 
            this.m_Buf[0] = (this.m_Buf[1] = 0x4D);
        else 
            this.m_Buf[0] = (this.m_Buf[1] = 0x49);
        fout.write(this.m_Buf, 0, 2);
        this.writeInt(fout, 0x2A, 2);
        this.writeInt(fout, 8, 4);
        let baseoff = this.m_IfdOffset[pageIndex];
        this.m_Stream.position = baseoff;
        let i = 0;
        let j = 0;
        let ii = 0;
        let dircount = this.readInt(2);
        let outpos = (8 + 2 + (12 * dircount)) + 4;
        let stripoffpos = 0;
        let stripoffitemsize = 0;
        let directData = false;
        let directDataPos = 0;
        let directDataLen = 0;
        let stripPos = new Array();
        let stripLen = new Array();
        this.writeInt(fout, dircount, 2);
        for (i = 0; i < dircount; i++) {
            this.m_Stream.position = baseoff + (2) + (i * 12);
            let tag = this.readInt(2);
            let typ = this.readInt(2);
            let cou = this.readInt(4);
            let dsp = this.readInt(4);
            let itemsize = 1;
            fout.position = 8 + 2 + (ii * 12);
            ii++;
            this.writeInt(fout, tag, 2);
            this.writeInt(fout, typ, 2);
            this.writeInt(fout, cou, 4);
            switch (typ) { 
            case 3: {
                itemsize = 2;
                break;
            }
            case 4: {
                itemsize = 4;
                break;
            }
            case 5: {
                itemsize = 8;
                break;
            }
            case 8: {
                itemsize = 2;
                break;
            }
            case 9: {
                itemsize = 4;
                break;
            }
            case 10: {
                itemsize = 8;
                break;
            }
            }
            let len = itemsize * cou;
            if (len <= 4 && tag !== 0x111) {
                this.writeInt(fout, dsp, 4);
                if (tag === 0x117) 
                    directDataLen = dsp;
                continue;
            }
            if (tag === 0x111) {
                stripoffpos = outpos;
                stripoffitemsize = itemsize;
                if (cou === 1) {
                    directData = true;
                    directDataPos = dsp;
                    stripoffpos = fout.position;
                    this.writeInt(fout, dsp, 4);
                    continue;
                }
                else {
                    this.m_Stream.position = dsp;
                    for (j = 0; j < cou; j++) {
                        stripPos.push(this.readInt(itemsize));
                    }
                    this.m_Stream.position = dsp;
                }
            }
            else if (tag === 0x117) {
                if (cou !== stripPos.length) {
                    fout.close();
                    throw new Error("StripOffsetCount != StripCount");
                }
                this.m_Stream.position = dsp;
                for (j = 0; j < cou; j++) {
                    stripLen.push(this.readInt(itemsize));
                }
                this.m_Stream.position = dsp;
            }
            this.readBuf(dsp, len);
            this.writeInt(fout, outpos, 4);
            fout.position = outpos;
            fout.write(this.m_Buf, 0, len);
            outpos += len;
            if (((outpos & 1)) === 1) {
                outpos++;
                this.writeInt(fout, 0, 1);
            }
        }
        fout.position = 8 + 2 + (ii * 12);
        this.writeInt(fout, 0, 4);
        fout.position = 8;
        this.writeInt(fout, ii, 2);
        if (directData) {
            this.readBuf(directDataPos, directDataLen);
            fout.position = stripoffpos;
            this.writeInt(fout, outpos, 4);
            fout.position = outpos;
            fout.write(this.m_Buf, 0, directDataLen);
            outpos += directDataLen;
        }
        else {
            fout.position = outpos;
            for (i = 0; i < stripPos.length; i++) {
                this.readBuf(stripPos[i], stripLen[i]);
                stripPos[i] = outpos;
                fout.write(this.m_Buf, 0, stripLen[i]);
                outpos += stripLen[i];
            }
            fout.position = stripoffpos;
            for (i = 0; i < stripPos.length; i++) {
                this.writeInt(fout, stripPos[i], stripoffitemsize);
            }
        }
    }
    
    /**
     * Извлечение текста, сформированного MODI (хранится в теге 932Fh)
     * @param pageIndex номер страницы
     * @return текст (null при ошибке или отсутствии)
     */
    getPageText(pageIndex) {
        const MiscHelper = require("./../../../util/MiscHelper");
        if ((pageIndex < 0) || pageIndex >= this.m_IfdOffset.length) 
            throw new Error("Page index error");
        let baseoff = this.m_IfdOffset[pageIndex];
        this.m_Stream.position = baseoff;
        let i = 0;
        let dircount = this.readInt(2);
        for (i = 0; i < dircount; i++) {
            this.m_Stream.position = baseoff + (2) + (i * 12);
            let tag = this.readInt(2);
            let typ = this.readInt(2);
            let cou = this.readInt(4);
            let dsp = this.readInt(4);
            if (tag !== 0x932F) 
                continue;
            this.m_Stream.position = dsp;
            if (this.readInt(2) !== 1) 
                return null;
            cou = this.readInt(4);
            if (cou < 1) 
                return null;
            let buf = new Uint8Array(cou);
            this.m_Stream.read(buf, 0, cou);
            let blen = buf.length;
            if (blen > 0 && buf[blen - 1] === (0)) 
                blen--;
            if (blen === 0) 
                return null;
            return MiscHelper.decodeStringUtf8(buf, 0, blen);
        }
        return null;
    }
    
    get hasText() {
        for (let p = 0; p < this.m_IfdOffset.length; p++) {
            let baseoff = this.m_IfdOffset[p];
            this.m_Stream.position = baseoff;
            let i = 0;
            let dircount = this.readInt(2);
            for (i = 0; i < dircount; i++) {
                this.m_Stream.position = baseoff + (2) + (i * 12);
                let tag = this.readInt(2);
                let typ = this.readInt(2);
                let cou = this.readInt(4);
                let dsp = this.readInt(4);
                if (tag === 0x932F) 
                    return true;
            }
        }
        return false;
    }
    
    /**
     * Это извлечение информации о самой картинке вместе с положением данных во входном потоке 
     * - используется для выделения CCITT-данных для PDF FaxDecode
     * @param pageIndex 
     * @param dataStartPos 
     * @param dataLength 
     * @param width 
     * @param height 
     * @return 
     */
    getDataInfo(pageIndex, dataStartPos, dataLength, width, height, ccittTyp, pageNumber) {
        const TiffHelperCCITT_Types = require("./TiffHelperCCITT_Types");
        dataStartPos.value = (dataLength.value = 0);
        pageNumber.value = 0;
        width.value = (height.value = 0);
        ccittTyp.value = TiffHelperCCITT_Types.G3_1D;
        if ((pageIndex < 0) || pageIndex >= this.m_IfdOffset.length) 
            throw new Error("Page index error");
        let baseoff = this.m_IfdOffset[pageIndex];
        this.m_Stream.position = baseoff;
        let i = 0;
        let dircount = this.readInt(2);
        for (i = 0; i < dircount; i++) {
            this.m_Stream.position = baseoff + (2) + (i * 12);
            let tag = this.readInt(2);
            let typ = this.readInt(2);
            let cou = this.readInt(4);
            let dsp = this.readInt(4);
            if (tag === 0x100) 
                width.value = dsp;
            else if (tag === 0x101) 
                height.value = dsp;
            else if (tag === 0x111) 
                dataStartPos.value = dsp;
            else if (tag === 0x117) 
                dataLength.value = dsp;
            else if (tag === 0x103) 
                ccittTyp.value = TiffHelperCCITT_Types.of(dsp);
            else if (tag === 0x129) {
            }
        }
        return dataLength.value > 0;
    }
    
    getPageInfo(pageIndex, width, height, dpi_x, dpi_y, isMono, _hasText) {
        width.value = (height.value = (dpi_x.value = (dpi_y.value = 0)));
        isMono.value = (_hasText.value = false);
        if ((pageIndex < 0) || pageIndex >= this.m_IfdOffset.length) 
            return;
        let baseoff = this.m_IfdOffset[pageIndex];
        this.m_Stream.position = baseoff;
        let i = 0;
        let dircount = this.readInt(2);
        let mono = -1;
        let resUnit = 2;
        let resXpos = 0;
        let resYpos = 0;
        for (i = 0; i < dircount; i++) {
            this.m_Stream.position = baseoff + (2) + (i * 12);
            let tag = this.readInt(2);
            let typ = this.readInt(2);
            let cou = this.readInt(4);
            let dsp = this.readInt(4);
            if (tag === 0x100) 
                width.value = dsp;
            else if (tag === 0x101) 
                height.value = dsp;
            else if (tag === 0x106 && (mono < 0)) 
                mono = ((dsp === 0 || dsp === 1) ? 1 : 0);
            else if (tag === 0x102 && dsp > 1) 
                mono = 0;
            else if (tag === 0x932F) 
                _hasText.value = true;
            else if (tag === 0x128) 
                resUnit = dsp;
            else if (tag === 0x11A) 
                resXpos = dsp;
            else if (tag === 0x11B) 
                resYpos = dsp;
        }
        isMono.value = mono === 1;
        if (((resUnit === 2 || resUnit === 3)) && resXpos > (0)) {
            this.m_Stream.position = resXpos;
            let v1 = this.readInt(4);
            let v2 = this.readInt(4);
            let a = v1;
            if (v2 > 1) 
                a /= (v2);
            if (resUnit === 3) 
                a *= 2.54;
            dpi_x.value = Math.floor(a);
        }
        if (((resUnit === 2 || resUnit === 3)) && resYpos > (0)) {
            this.m_Stream.position = resYpos;
            let v1 = this.readInt(4);
            let v2 = this.readInt(4);
            let a = v1;
            if (v2 > 1) 
                a /= (v2);
            if (resUnit === 3) 
                a *= 2.54;
            dpi_y.value = Math.floor(a);
        }
    }
    
    /**
     * Создание TIFF по чистым данным без заголовка (фактически обрамляет заголовком)
     * @param res 
     * @param width 
     * @param height 
     * @param typ 
     * @param xResolution 
     * @param yResolution 
     * @param data 
     */
    writeTiff(res, width, height, typ, xResolution, yResolution, invert, data) {
        res.writeByte(0x49);
        res.writeByte(0x49);
        this._WriteInt(res, 0x2A, 2);
        this._WriteInt(res, (8 + data.length + 8) + 8, 4);
        res.write(data, 0, data.length);
        this._WriteInt(res, Math.floor(xResolution), 4);
        this._WriteInt(res, 1, 4);
        this._WriteInt(res, Math.floor(yResolution), 4);
        this._WriteInt(res, 1, 4);
        let dirs = 0;
        this._WriteInt(res, 0, 2);
        this.writeDirEntry(res, 0xFE, 4, 1, 0);
        dirs++;
        this.writeDirEntry(res, 0x100, 3, 1, width);
        dirs++;
        this.writeDirEntry(res, 0x101, 3, 1, height);
        dirs++;
        this.writeDirEntry(res, 0x102, 3, 1, 1);
        dirs++;
        this.writeDirEntry(res, 0x103, 3, 1, typ.value());
        dirs++;
        this.writeDirEntry(res, 0x106, 3, 1, (invert ? 1 : 0));
        dirs++;
        this.writeDirEntry(res, 0x111, 4, 1, 8);
        dirs++;
        this.writeDirEntry(res, 0x115, 3, 1, 1);
        dirs++;
        this.writeDirEntry(res, 0x116, 3, 1, height);
        dirs++;
        this.writeDirEntry(res, 0x117, 4, 1, data.length);
        dirs++;
        this.writeDirEntry(res, 0x11A, 5, 1, 8 + data.length);
        dirs++;
        this.writeDirEntry(res, 0x11B, 5, 1, 8 + data.length + 8);
        dirs++;
        this.writeDirEntry(res, 0x128, 3, 1, 2);
        dirs++;
        this._WriteInt(res, 0, 4);
        res.position = (8 + data.length + 8) + 8;
        this._WriteInt(res, dirs, 2);
    }
    
    readInt(count) {
        let res = 0;
        for (let i = 0; i < count; i++) {
            let j = this.m_Stream.readByte();
            if (j < 0) 
                break;
            if (this.m_Msb) 
                res |= j << (((count - 1 - i)) << 3);
            else 
                res |= j << (i << 3);
        }
        return res;
    }
    
    readBuf(pos, size) {
        if (this.m_Buf.length < size) 
            this.m_Buf = new Uint8Array(size);
        this.m_Stream.position = pos;
        this.m_Stream.read(this.m_Buf, 0, size);
    }
    
    writeInt(fOut, val, count) {
        for (let i = 0; i < count; i++) {
            let j = 0;
            if (this.m_Msb) 
                j = ((val >> (((count - 1 - i)) << 3))) & 0xFF;
            else 
                j = ((val >> (i << 3))) & 0xFF;
            this.fBuf2[i] = j;
        }
        fOut.write(this.fBuf2, 0, count);
    }
    
    _WriteInt(fOut, val, count) {
        for (let i = 0; i < count; i++) {
            let j = ((val >> (i << 3))) & 0xFF;
            this.fBuf2[i] = j;
        }
        fOut.write(this.fBuf2, 0, count);
    }
    
    writeDirEntry(fOut, tag, typ, count, data) {
        this._WriteInt(fOut, tag, 2);
        this._WriteInt(fOut, typ, 2);
        this._WriteInt(fOut, count, 4);
        this._WriteInt(fOut, data, 4);
    }
}


module.exports = TiffHelper