# 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

import typing
import io
import math
from pullenti.unisharp.Utils import Utils
from pullenti.unisharp.Misc import RefOutArgWrapper
from pullenti.unisharp.Streams import Stream

from pullenti.util.ArchiveHelper import ArchiveHelper
from pullenti.unitext.internal.pdf.PdfObject import PdfObject
from pullenti.unitext.internal.misc.LzwDecoder import LzwDecoder
from pullenti.util.MiscHelper import MiscHelper
from pullenti.unitext.internal.misc.PngWrapper import PngWrapper
from pullenti.unitext.FileFormat import FileFormat
from pullenti.unitext.internal.pdf.PdfReference import PdfReference
from pullenti.unitext.internal.pdf.PdfIntValue import PdfIntValue
from pullenti.util.FileFormatsHelper import FileFormatsHelper
from pullenti.unitext.UnitextImage import UnitextImage
from pullenti.unitext.internal.pdf.PdfBoolValue import PdfBoolValue
from pullenti.unitext.internal.pdf.PdfName import PdfName
from pullenti.unitext.internal.pdf.PdfStringValue import PdfStringValue
from pullenti.unitext.internal.pdf.PdfRealValue import PdfRealValue
from pullenti.unitext.internal.pdf.PdfArray import PdfArray

class PdfDictionary(PdfObject):
    
    def __init__(self) -> None:
        super().__init__()
        self.__m_items = dict()
        self.__m_stream_data = None
        self.__m_stream_in = None
        self.__m_stream_pos = 0
        self.__m_stream_length = 0
        self._m_file_pos = 0
        self.page_num = 0
        self.obj = None;
    
    def get_data(self) -> bytearray:
        if (self.__m_stream_data is None): 
            if (self.__m_stream_length == 0): 
                return None
            self.__m_stream_data = Utils.newArrayOfBytes(self.__m_stream_length, 0)
            self.__m_stream_in.position = self.__m_stream_pos
            self.__m_stream_in.read(self.__m_stream_data, 0, self.__m_stream_length)
        return self.__m_stream_data
    
    def set_data(self, dat : bytearray) -> None:
        self.__m_stream_data = dat
    
    def add_object(self, key : str, o : 'PdfObject') -> None:
        if (not key in self.__m_items): 
            if (o is not None): 
                self.__m_items[key] = o
        elif (o is not None): 
            self.__m_items[key] = o
        else: 
            del self.__m_items[key]
    
    @property
    def keys(self) -> typing.List[str]:
        return list(self.__m_items.keys())
    
    def is_simple(self, lev : int) -> bool:
        if (len(self.__m_items) > 10 or lev > 10): 
            return False
        for key in self.keys: 
            if (key == "Parent"): 
                continue
            it = self.get_object(key, False)
            if (it is not None and not it.is_simple(lev + 1)): 
                return False
        return True
    
    def to_string_ex(self, lev : int) -> str:
        if (lev > 5): 
            return "<<...{0}>>".format(len(self.__m_items))
        res = io.StringIO()
        it = self.get_object("Type", False)
        if (it is not None and lev >= 0): 
            print(str(it), end="", file=res)
        if (self.page_num > 0): 
            print(" Num={0}".format(self.page_num), end="", file=res, flush=True)
        if (self.id0_ > 0): 
            print(" Id={0}".format(self.id0_), end="", file=res, flush=True)
        print("<<", end="", file=res)
        fi = True
        partial = False
        for key in self.keys: 
            if (key == "Parent" or key == "Type"): 
                continue
            it = self.get_object(key, False)
            if (it is None): 
                indir = self.__m_items[key]
                if (not fi): 
                    print(", ", end="", file=res)
                else: 
                    fi = False
                print("{0}={1}".format(key, ("NULL" if indir is None else str(indir))), end="", file=res, flush=True)
                if (res.tell() > 100): 
                    break
                continue
            if (not it.is_simple(lev + 1)): 
                partial = True
                continue
            if (not fi): 
                print(", ", end="", file=res)
            else: 
                fi = False
            if (res.tell() > 100): 
                partial = True
                break
            str0_ = it.to_string_ex(lev + 1)
            if (len(str0_) > 50): 
                str0_ = (str0_[0:0+50] + "...")
            print("{0}={1}".format(key, str0_), end="", file=res, flush=True)
        if (partial): 
            print("...{0}".format(len(self.__m_items)), end="", file=res, flush=True)
        print(">>", end="", file=res)
        if (self.__m_stream_length > 0): 
            print(" DATA:{0}".format(self.__m_stream_length), end="", file=res, flush=True)
        return Utils.toStringStringIO(res)
    
    def out_html(self, res : io.StringIO) -> None:
        from pullenti.unitext.internal.pdf.PdfFont import PdfFont
        from pullenti.unitext.internal.pdf.PdfImage import PdfImage
        from pullenti.unitext.internal.pdf.PdfPage import PdfPage
        from pullenti.unitext.internal.pdf.PdfPageOper import PdfPageOper
        if (self.id0_ > 0): 
            print("Id={0} ".format(self.id0_), end="", file=res, flush=True)
        print("<b>&lt;&lt;</b> ", end="", file=res)
        fi = True
        for k in self.keys: 
            if (fi): 
                fi = False
            else: 
                print(", <br/>", end="", file=res)
            print("{0} : ".format(k), end="", file=res, flush=True)
            o = self.get_object(k, False)
            if (o is None): 
                print("null", end="", file=res)
                continue
            if (o.id0_ > 0): 
                print("<a href=\"obj{0}\">".format(o.id0_), end="", file=res, flush=True)
                MiscHelper.correct_html_value(res, str(o), False, False, False)
                print("</a>", end="", file=res)
            else: 
                o.out_html(res)
        print("<b> &gt;&gt;</b>", end="", file=res)
        self._out_html_refs(res)
        page = None
        if (self.is_type_item("Page")): 
            page = PdfPage(self, None, True)
        elif (self.is_type_item("XObject") and self.get_string_item("Subtype") == "Form"): 
            li = self.source_file.get_all_who_ref_object0(self.id0_)
            for o in li: 
                if ((isinstance(o, PdfDictionary)) and o.is_type_item("Page")): 
                    page0 = PdfPage(Utils.asObjectOrNull(o, PdfDictionary), None, True)
                    if (page0.subpages is not None): 
                        for p in page0.subpages: 
                            if (p.dic == self): 
                                page = p
                                break
                    break
        if (page is not None and page.lexems is not None): 
            print("\r\n<br/><hr/>", end="", file=res)
            opers = PdfPageOper.parse(page.lexems)
            for op in opers: 
                op.out_html(res)
                print("<br/>", end="", file=res)
        elif (self.is_type_item("Font")): 
            print("\r\n<br/><hr/>", end="", file=res)
            font = PdfFont(self)
            MiscHelper.correct_html_value(res, font.out_debug(), False, False, False)
        elif (self.is_type_item("Image") or ((self.is_type_item("XObject") and self.get_string_item("Subtype") == "Image"))): 
            print("\r\n<br/><hr/>", end="", file=res)
            img = PdfImage(self)
            if (img.content is not None): 
                frm = FileFormatsHelper.analize_format(None, img.content)
                print("<div>Image Format: <b>{0}</b></div>".format(Utils.enumToString(frm)), end="", file=res, flush=True)
                uimg = UnitextImage()
                uimg.content = img.content
                if (img.width > 0): 
                    uimg.width = "{0}pt".format(math.floor(img.width))
                if (img.height > 0): 
                    uimg.height = "{0}pt".format(math.floor(img.height))
                print("<div style=\"border:1pt solid red\">", end="", file=res)
                uimg.get_html(res, None)
                print("</div>", end="", file=res)
        elif (self.has_data_stream): 
            print("\r\n<br/><hr/>DataStream:<br/>", end="", file=res)
            try: 
                dat = self.extract_data()
                if (dat is None): 
                    dat = self.get_data()
                if (dat is not None): 
                    has_zero = 0
                    zind = -1
                    i = 0
                    while i < len(dat): 
                        if (dat[i] == (0)): 
                            has_zero += 1
                            if (zind < 0): 
                                zind = i
                            break
                        i += 1
                    if ((has_zero < 3) or zind > 1000): 
                        len0_ = len(dat)
                        if (len(dat) > 30000): 
                            len0_ = 30000
                        ch0 = chr(0)
                        ch1 = chr(1)
                        txt = dat[0:0+len0_].decode("ASCII", 'ignore').replace("{0}".format(ch0), "{0}").replace("{0}".format(ch1), "{1}")
                        MiscHelper.correct_html_value(res, txt, False, False, False)
                    else: 
                        print("<div style=\"font-family:monospace\">", end="", file=res)
                        i = 0
                        while (i < len(dat)) and (i < 1000): 
                            if (((i & 0xF)) == 0): 
                                print("\n<br/>", end="", file=res)
                            print("{0} ".format("{:02X}".format(dat[i])), end="", file=res, flush=True)
                            if (res.tell() > 30000): 
                                print("\r\n<br/>...", end="", file=res)
                                break
                            i += 1
                        print("</div>", end="", file=res)
            except Exception as ex: 
                print("ERROR: ", end="", file=res)
                MiscHelper.correct_html_value(res, str(ex), False, False, False)
    
    @property
    def has_data_stream(self) -> bool:
        return self.__m_stream_length > 0
    
    def _post_parse(self, stream : 'PdfStream') -> None:
        self.__m_items.clear()
        self.__m_stream_in = (None)
        self.__m_stream_length = 0
        self.__m_stream_data = (None)
        p0 = stream.position
        ch = 0
        i = 0
        str0_ = None
        while True:
            i = stream.peek_solid_byte()
            if (((i)) >= 0): pass
            else: 
                break
            ch = (i)
            if ((chr(ch)) == '/'): 
                str0_ = PdfStringValue.get_string_by_bytes(stream.read_name())
                if (str0_ is None): 
                    break
                if (str0_ == "Contents"): 
                    pass
                obj_ = stream.parse_object(self.source_file, False)
                if (obj_ is None): 
                    break
                if (not str0_ in self.__m_items): 
                    self.__m_items[str0_] = obj_
                continue
            if ((chr(ch)) != '>'): 
                break
            stream.position = stream.position + 1
            ch = (stream.read_byte())
            if ((chr(ch)) != '>'): 
                break
            self.__parse_stream(stream)
            return
    
    def __parse_stream(self, stream : 'PdfStream') -> None:
        p0 = stream.position
        str0_ = stream.read_word(False)
        if (str0_ != "stream"): 
            stream.position = p0
            return
        stream_tag = Utils.newArrayOfBytes(6, 0)
        ch = stream.read_byte()
        if (ch != (0xD)): 
            stream.position = stream.position - 1
        ch = (stream.read_byte())
        if (ch != (0xA)): 
            stream.position = stream.position - 1
        self.__m_stream_in = stream
        self.__m_stream_pos = stream.position
        self.__m_stream_length = (self.get_int_item("Length"))
        if (self.__m_stream_length > 0 and ((self.__m_stream_pos + self.__m_stream_length) < stream.length)): 
            stream.position = self.__m_stream_pos + self.__m_stream_length
        len0_ = 0
        i = 0
        while True:
            i = stream.read_byte()
            if (((i)) >= 0): pass
            else: 
                break
            ch = (i)
            len0_ += 1
            if ((chr(ch)) != 'e'): 
                continue
            ch = (stream.read_byte())
            len0_ += 1
            if ((chr(ch)) != 'n'): 
                continue
            ch = (stream.read_byte())
            len0_ += 1
            if ((chr(ch)) != 'd'): 
                continue
            p0 = stream.position
            stream.read(stream_tag, 0, 6)
            if (PdfStringValue.get_string_by_bytes(stream_tag) == "stream"): 
                len0_ -= 3
                break
            stream.position = p0
        if (self.__m_stream_length == 0): 
            self.__m_stream_length = len0_
    
    def get_string_item(self, key : str) -> str:
        val = self.get_object(key, False)
        while True:
            if (val is None): 
                return None
            if (isinstance(val, PdfName)): 
                return val.name
            if (isinstance(val, PdfBoolValue)): 
                return ("true" if val.val else "false")
            if (isinstance(val, PdfStringValue)): 
                return PdfStringValue.get_string_by_bytes(val.val)
            if (isinstance(val, PdfIntValue)): 
                return str(val.val)
            if (isinstance(val, PdfArray)): 
                val = val.get_item(0, False)
                if (val is None): 
                    break
                continue
            break
        return None
    
    def is_type_item(self, type_val : str) -> bool:
        return self.get_string_item("Type") == type_val
    
    def get_int_item(self, key : str) -> int:
        val = self.get_object(key, False)
        if (val is None): 
            return 0
        if (isinstance(val, PdfIntValue)): 
            return val.val
        if (isinstance(val, PdfRealValue)): 
            return math.floor(val.get_double())
        return 0
    
    def get_object(self, key : str, keep_ref : bool=False) -> 'PdfObject':
        res = None
        wrapres176 = RefOutArgWrapper(None)
        inoutres177 = Utils.tryGetValue(self.__m_items, key, wrapres176)
        res = wrapres176.value
        if (not inoutres177): 
            return None
        if ((isinstance(res, PdfReference)) and not keep_ref): 
            res = self.source_file.get_object(res.id0_)
            if (res is None): 
                return None
            self.__m_items[key] = res
        return res
    
    def get_dictionary(self, key : str, typ_val : str=None) -> 'PdfDictionary':
        obj_ = self.get_object(key, False)
        if (isinstance(obj_, PdfArray)): 
            obj_ = obj_.get_item(0, False)
        if (isinstance(obj_, PdfDictionary)): 
            res = Utils.asObjectOrNull(obj_, PdfDictionary)
            if (typ_val is not None): 
                if (not res.is_type_item(typ_val)): 
                    return None
            return res
        return None
    
    def get_all_pages(self, res : typing.List['PdfDictionary']) -> None:
        if (self.is_type_item("Page")): 
            res.append(self)
            return
        dic = self.get_dictionary("Pages", "Pages")
        if (dic is not None): 
            dic.get_all_pages(res)
        else: 
            kids = Utils.asObjectOrNull(self.get_object("Kids", False), PdfArray)
            if (kids is not None): 
                i = 0
                while i < kids.items_count: 
                    it = kids.get_item(i, False)
                    if (isinstance(it, PdfDictionary)): 
                        it.get_all_pages(res)
                    i += 1
    
    def extract_data(self) -> bytearray:
        dat = self.get_data()
        if (dat is None): 
            return Utils.newArrayOfBytes(0, 0)
        str0_ = self.get_string_item("Filter")
        if (Utils.isNullOrEmpty(str0_)): 
            return dat
        if (len(dat) < 10): 
            return None
        res = None
        if (str0_ == "FlateDecode"): 
            res = ArchiveHelper.decompress_zlib(dat)
        elif (str0_ == "LZWDecode"): 
            lzw = LzwDecoder()
            res = lzw.decode(dat)
        else: 
            return None
        if (res is None or len(res) == 0): 
            return None
        dparms = self.get_dictionary("DecodeParms", None)
        predict = 0
        n = 0
        columns = 0
        if (dparms is not None): 
            iv = Utils.asObjectOrNull(dparms.get_object("Predictor", False), PdfIntValue)
            if (iv is not None and iv.val > 1): 
                predict = iv.val
            iv = (Utils.asObjectOrNull(dparms.get_object("Columns", False), PdfIntValue))
            if (iv is not None): 
                columns = iv.val
            iv = (Utils.asObjectOrNull(dparms.get_object("Colors", False), PdfIntValue))
            if (iv is not None): 
                n = iv.val
        if ((predict >= 10 and predict <= 15 and n > 0) and columns > 0): 
            row_size = columns * n
            if (predict == 15): 
                row_size += 1
            pos0 = 0
            while (pos0 + row_size) < len(res): 
                cod = predict - 10
                pos = pos0
                if (predict == 15): 
                    cod = (res[pos0])
                    pos += 1
                j = pos
                while j < (pos0 + row_size): 
                    val_left = 0
                    val_top = 0
                    val_corner = 0
                    k = j - n
                    kk = j - row_size
                    if (kk >= 0): 
                        val_top = res[kk]
                    if (k >= pos): 
                        val_left = res[k]
                        if (kk >= 0): 
                            val_corner = res[kk - n]
                    res[j] = PngWrapper.filter_byte(cod, False, res[j], val_left, val_top, val_corner)
                    j += 1
                pos0 += row_size
            if (predict == 15): 
                p = 0
                i = 0
                while i < len(res): 
                    if (((i % row_size)) == 0): 
                        pass
                    else: 
                        res[p] = res[i]
                        p += 1
                    i += 1
                res1 = Utils.newArrayOfBytes(p, 0)
                i = 0
                while i < p: 
                    res1[i] = res[i]
                    i += 1
                res = res1
        return res
    
    def set_data_value(self, data : bytearray) -> None:
        filt = Utils.asObjectOrNull(self.get_object("Filter", False), PdfName)
        if (filt is not None): 
            if (filt.name == "FlateDecode" or filt.name == "LZWDecode"): 
                filt.name = "FlateDecode"
                data = ArchiveHelper.compress_zlib(data)
        self.__m_stream_data = data
    
    def get_total_data_stream(self, key : str) -> bytearray:
        obj_ = self.get_object(key, False)
        if (obj_ is None): 
            return None
        if (isinstance(obj_, PdfDictionary)): 
            return obj_.extract_data()
        arr = Utils.asObjectOrNull(obj_, PdfArray)
        if (arr is None): 
            return None
        res = None
        i = 0
        first_pass702 = True
        while True:
            if first_pass702: first_pass702 = False
            else: i += 1
            if (not (i < arr.items_count)): break
            obj_ = arr.get_item(i, False)
            if (obj_ is None): 
                continue
            if (not (isinstance(obj_, PdfDictionary))): 
                continue
            tmp = obj_.extract_data()
            if (tmp is None): 
                continue
            if (len(tmp) < 1): 
                continue
            if (res is None): 
                res = bytearray(tmp)
            else: 
                res.append(0xD)
                res.append(0xA)
                res.extend(tmp)
        if (res is None): 
            return None
        return bytearray(res)
    
    def clear_total_data_stream(self, key : str) -> None:
        obj_ = self.get_object(key, False)
        if (obj_ is None): 
            return
        if (isinstance(obj_, PdfDictionary)): 
            obj_.set_data(None)
        arr = Utils.asObjectOrNull(obj_, PdfArray)
        if (arr is None): 
            return
        i = 0
        first_pass703 = True
        while True:
            if first_pass703: first_pass703 = False
            else: i += 1
            if (not (i < arr.items_count)): break
            obj_ = arr.get_item(i, False)
            if (obj_ is None): 
                continue
            if (not (isinstance(obj_, PdfDictionary))): 
                continue
            obj_.set_data(None)
    
    def write0_(self, s : Stream) -> None:
        if (self.id0_ == 18): 
            pass
        s.writebyte(ord('<'))
        s.writebyte(ord('<'))
        buf = [ ]
        fi = True
        dat = self.get_data()
        has_size = False
        if (dat is not None): 
            if (len(dat) == 448): 
                pass
        for kp in self.__m_items.items(): 
            it = kp[1]
            if (it.deleted): 
                continue
            if (kp[0] == "Length" and dat is not None and (isinstance(it, PdfIntValue))): 
                has_size = True
                it.val = len(dat)
            if (fi): 
                fi = False
            else: 
                s.writebyte(ord(' '))
            buf = "/{0}".format(kp[0]).encode("ASCII", 'ignore')
            s.write(buf, 0, len(buf))
            s.writebyte(ord(' '))
            if (it.id0_ == 0 or kp[0] == "Length"): 
                it.write0_(s)
            else: 
                buf = "{0} 0 R".format(it.write_id).encode("ASCII", 'ignore')
                s.write(buf, 0, len(buf))
        if (not has_size and dat is not None): 
            if (fi): 
                fi = False
            else: 
                s.writebyte(ord(' '))
            buf = "/Length {0}".format(len(dat)).encode("ASCII", 'ignore')
            s.write(buf, 0, len(buf))
        s.writebyte(ord('>'))
        s.writebyte(ord('>'))
        if (dat is None): 
            return
        buf = "stream\r\n".encode("ASCII", 'ignore')
        s.write(buf, 0, len(buf))
        s.write(dat, 0, len(dat))
        buf = "endstream".encode("ASCII", 'ignore')
        s.write(buf, 0, len(buf))
    
    def get_all_references(self, refs : typing.List['PdfReference'], ids : typing.List[int]) -> None:
        if (self.id0_ > 0): 
            if (self.id0_ in ids): 
                return
            ids.append(self.id0_)
        for k in self.keys: 
            o = self.get_object(k, True)
            if (o is not None): 
                o.get_all_references(refs, ids)
        if (self.id0_ > 0): 
            ids.remove(self.id0_)
    
    def get_all_who_ref_object(self, obj_id : int, cur : 'PdfObject', res : typing.List['PdfObject'], ids : typing.List[int]) -> None:
        if (self.id0_ > 0): 
            if (self.id0_ in ids): 
                return
            ids.append(self.id0_)
            cur = (self)
        for k in self.keys: 
            o = self.get_object(k, True)
            if (o is not None and o.id0_ == obj_id): 
                if (not cur in res): 
                    res.append(cur)
            elif (o is not None and o.id0_ == 0): 
                o.get_all_who_ref_object(obj_id, cur, res, ids)
        if (self.id0_ > 0): 
            ids.remove(self.id0_)
    
    def set_new_data_content(self, data : bytearray) -> None:
        if (self.is_type_item("XObject")): 
            self.set_data_value(data)
            return
        cnts = self.get_object("Contents", False)
        if (isinstance(cnts, PdfDictionary)): 
            cnts.set_data_value(data)
        elif (isinstance(cnts, PdfArray)): 
            arr = Utils.asObjectOrNull(cnts, PdfArray)
            if (arr.items_count > 0): 
                ddic = None
                len0_ = 0
                i = 0
                first_pass704 = True
                while True:
                    if first_pass704: first_pass704 = False
                    else: i += 1
                    if (not (i < arr.items_count)): break
                    cc = Utils.asObjectOrNull(arr.get_item(i, False), PdfDictionary)
                    if (cc is None): 
                        continue
                    le = cc.get_int_item("Length")
                    if (le > len0_): 
                        len0_ = le
                        ddic = cc
                if (ddic is not None): 
                    ddic.set_data_value(data)
                    for i in range(arr.items_count - 1, -1, -1):
                        oo = arr.get_item(i, False)
                        if (oo is not None and oo != ddic): 
                            oo.deleted = True
                        if (oo != ddic): 
                            arr.remove_item(i)
        elif (self.has_data_stream): 
            self.set_data_value(data)
        else: 
            pass
    
    @staticmethod
    def _new244(_arg1 : int) -> 'PdfDictionary':
        res = PdfDictionary()
        res._m_file_pos = _arg1
        return res