/*
 *
 * This file is part of the iText (R) project.
    Copyright (c) 1998-2022 iText Group NV
 * Authors: Bruno Lowagie, Paulo Soares, et al.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License version 3
 * as published by the Free Software Foundation with the addition of the
 * following permission added to Section 15 as permitted in Section 7(a):
 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
 * ITEXT GROUP. ITEXT GROUP DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
 * OF THIRD PARTY RIGHTS
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details.
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA, 02110-1301 USA, or download the license from the following URL:
 * http://itextpdf.com/terms-of-use/
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License,
 * a covered work must retain the producer line in every PDF that is created
 * or manipulated using iText.
 *
 * You can be released from the requirements of the license by purchasing
 * a commercial license. Buying such a license is mandatory as soon as you
 * develop commercial activities involving the iText software without
 * disclosing the source code of your own applications.
 * These activities include: offering paid services to customers as an ASP,
 * serving PDFs on the fly in a web application, shipping iText with a closed
 * source product.
 *
 * For more information, please contact iText Software Corp. at this
 * address: sales@itextpdf.com
 */
package com.itextpdf.text.pdf;

import com.itextpdf.text.*;
import com.itextpdf.text.error_messages.MessageLocalization;
import com.itextpdf.text.pdf.interfaces.IAccessibleElement;
import com.itextpdf.text.pdf.interfaces.IPdfStructureElement;
import com.itextpdf.text.pdf.internal.PdfIsoKeys;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;

/**
 * This is a node in a document logical structure. It may contain a mark point or it may contain
 * other nodes.
 * @author Paulo Soares
 */
public class PdfStructureElement extends PdfDictionary implements IPdfStructureElement{
    
    /**
     * Holds value of property kids.
     */
    private transient PdfStructureElement parent;
    private transient PdfStructureTreeRoot top;

    private AccessibleElementId elementId;

    /**
     * Holds value of property reference.
     */
    private PdfIndirectReference reference;

    private PdfName structureType;

    /**
     * Creates a new instance of PdfStructureElement.
     * @param parent the parent of this node
     * @param structureType the type of structure. It may be a standard type or a user type mapped by the role map
     */
    public PdfStructureElement(PdfStructureElement parent, PdfName structureType) {
        top = parent.top;
        init(parent, structureType);
        this.parent = parent;
        put(PdfName.P, parent.reference);
        put(PdfName.TYPE, PdfName.STRUCTELEM);
    }

    /**
     * Creates a new instance of PdfStructureElement.
     * @param root the parent of this node
     * @param structureType the type of structure. It may be a standard type or a user type mapped by the role map
     */
    public PdfStructureElement(PdfStructureTreeRoot root, PdfName structureType) {
        top = root;

        init(root, structureType);
        put(PdfName.P, root.getReference());
        put(PdfName.TYPE, PdfName.STRUCTELEM);
    }

    protected PdfStructureElement(PdfDictionary parent, PdfName structureType, AccessibleElementId elementId) {
        this.elementId = elementId;
        if (parent instanceof PdfStructureElement) {
            top = ((PdfStructureElement)parent).top;
            init(parent, structureType);
            this.parent = (PdfStructureElement)parent;
            put(PdfName.P, ((PdfStructureElement)parent).reference);
            put(PdfName.TYPE, PdfName.STRUCTELEM);
        } else if (parent instanceof PdfStructureTreeRoot) {
            top = (PdfStructureTreeRoot)parent;
            init(parent, structureType);
            put(PdfName.P, ((PdfStructureTreeRoot)parent).getReference());
            put(PdfName.TYPE, PdfName.STRUCTELEM);
        } else {

        }
    }

    public PdfName getStructureType() {
        return structureType;
    }

    private void init(PdfDictionary parent, PdfName structureType) {
        if (!top.getWriter().getStandardStructElems().contains(structureType)) {
            PdfDictionary roleMap = top.getAsDict(PdfName.ROLEMAP);
            if (roleMap == null || !roleMap.contains(structureType))
                throw new ExceptionConverter(new DocumentException(MessageLocalization.getComposedMessage("unknown.structure.element.role.1", structureType.toString())));
            else
                this.structureType = roleMap.getAsName(structureType);
        } else {
            this.structureType = structureType;
        }
        PdfObject kido = parent.get(PdfName.K);
        PdfArray kids = null;
        if (kido == null) {
            kids = new PdfArray();
            parent.put(PdfName.K, kids);
        } else if (kido instanceof PdfArray) {
            kids = (PdfArray)kido;
        } else {
            kids = new PdfArray();
            kids.add(kido);
            parent.put(PdfName.K, kids);
        }
        if (kids.size() > 0) {
            if (kids.getAsNumber(0) != null)
                kids.remove(0);
            if (kids.size() > 0) {
                PdfDictionary mcr = kids.getAsDict(0);
                if (mcr != null && PdfName.MCR.equals(mcr.getAsName(PdfName.TYPE))) {
                    kids.remove(0);
                }
            }
        }
        put(PdfName.S, structureType);
        reference = top.getWriter().getPdfIndirectReference();
        kids.add(this.reference);
    }

    /**
     * Gets the parent of this node.
     * @return the parent of this node
     */
    public PdfDictionary getParent() {
        return getParent(false);
    }

    public PdfDictionary getParent(boolean includeStructTreeRoot) {
        if (parent == null && includeStructTreeRoot)
            return top;
        else
            return parent;
    }

    void setPageMark(int page, int mark) {
        if (mark >= 0)
            put(PdfName.K, new PdfNumber(mark));
        top.setPageMark(page, reference);
    }

    void setAnnotation(PdfAnnotation annot, PdfIndirectReference currentPage) {
        PdfArray kArray = getAsArray(PdfName.K);
        if (kArray == null) {
            kArray = new PdfArray();
            PdfObject k = get(PdfName.K);
            if (k != null) {
                kArray.add(k);
            }
            put(PdfName.K, kArray);
        }
        PdfDictionary dict = new PdfDictionary();
        dict.put(PdfName.TYPE, PdfName.OBJR);
        dict.put(PdfName.OBJ, annot.getIndirectReference());
        if (annot.getRole() == PdfName.FORM)
            dict.put(PdfName.PG, currentPage);
        kArray.add(dict);
    }

    /**
     * Gets the reference this object will be written to.
     * @return the reference this object will be written to
     * @since	2.1.6 method removed in 2.1.5, but restored in 2.1.6
     */    
    public PdfIndirectReference getReference() {
        return this.reference;
    }

    /**
     * Gets the first entarance of attribute.
     * @returns PdfObject
     * @since 5.3.4
     */
    public PdfObject getAttribute(PdfName name){
        PdfDictionary attr = getAsDict(PdfName.A);
        if (attr != null){
            if (attr.contains(name))
                return attr.get(name);
        }
        PdfDictionary parent = getParent();
        if (parent instanceof PdfStructureElement)
            return ((PdfStructureElement) parent).getAttribute(name);
        if (parent instanceof PdfStructureTreeRoot)
            return  ((PdfStructureTreeRoot) parent).getAttribute(name);

        return new PdfNull();
    }

    /**
     * Sets the attribute value.
     * @since 5.3.4
     */
    public void setAttribute(PdfName name, PdfObject obj){
        PdfDictionary attr = getAsDict(PdfName.A);
        if (attr == null){
            attr = new PdfDictionary();
            put(PdfName.A, attr);
        }
        attr.put(name, obj);
    }

    public void writeAttributes(final IAccessibleElement element) {
// I do remember that these lines were necessary to avoid creation of files which are not valid from Acrobat 10 preflight perspective.
// Now it seems that in Acrobat 11 there's no such problem (I think Acrobat 10 behavior can be considered as a bug) and we can remove those lines.
//        if (top.getWriter().getPdfVersion().getVersion() < PdfWriter.VERSION_1_7)
//            return;

        if (element instanceof ListItem) {
            writeAttributes((ListItem)element);
        } else if (element instanceof Paragraph) {
            writeAttributes((Paragraph) element);
        } else if (element instanceof Chunk) {
            writeAttributes((Chunk)element);
        } else if (element instanceof Image) {
            writeAttributes((Image)element);
        } else if (element instanceof List) {
            writeAttributes((List)element);
        } else if (element instanceof ListLabel) {
            writeAttributes((ListLabel)element);
        } else if (element instanceof ListBody) {
            writeAttributes((ListBody)element);
        } else if (element instanceof PdfPTable) {
            writeAttributes((PdfPTable)element);
        } else if (element instanceof PdfPRow) {
            writeAttributes((PdfPRow)element);
        } else if (element instanceof PdfPHeaderCell) {
            writeAttributes((PdfPHeaderCell)element);
        } else if (element instanceof PdfPCell) {
            writeAttributes((PdfPCell)element);
        } else if (element instanceof PdfPTableHeader) {
            writeAttributes((PdfPTableHeader)element);
        } else if (element instanceof PdfPTableFooter) {
            writeAttributes((PdfPTableFooter)element);
        } else if (element instanceof PdfPTableBody) {
            writeAttributes((PdfPTableBody)element);
        } else if (element instanceof PdfDiv) {
            writeAttributes((PdfDiv)element);
        } else if (element instanceof PdfTemplate) {
            writeAttributes((PdfTemplate)element);
        } else if (element instanceof Document) {
            writeAttributes((Document)element);
        }
        if (element.getAccessibleAttributes() != null) {
            for (PdfName key : element.getAccessibleAttributes().keySet()) {
                if (key.equals(PdfName.ID)) {
                    PdfObject attr = element.getAccessibleAttribute(key);
                    put(key, attr);
                    top.putIDTree(attr.toString(), getReference());
                }
                else if (key.equals(PdfName.LANG) || key.equals(PdfName.ALT) || key.equals(PdfName.ACTUALTEXT) || key.equals(PdfName.E) || key.equals(PdfName.T)) {
                    put(key, element.getAccessibleAttribute(key));
                } else {
                    setAttribute(key, element.getAccessibleAttribute(key));
                }
            }
        }
    }

    private void writeAttributes(final Chunk chunk) {
        if (chunk != null) {
            if (chunk.getImage() != null) {
                writeAttributes(chunk.getImage());
            } else {
                HashMap<String, Object> attr = chunk.getAttributes();
                if (attr != null){
                    this.setAttribute(PdfName.O, PdfName.LAYOUT);
                    // Setting non-inheritable attributes
                    if (attr.containsKey(Chunk.UNDERLINE)){
                        this.setAttribute(PdfName.TEXTDECORATIONTYPE, PdfName.UNDERLINE);
                    }
                    if (attr.containsKey(Chunk.BACKGROUND)){
                        Object[] back = (Object[])attr.get(Chunk.BACKGROUND);
                        BaseColor color = (BaseColor)back[0];
                        this.setAttribute(PdfName.BACKGROUNDCOLOR, new PdfArray(new float[] {color.getRed()/255f, color.getGreen()/255f, color.getBlue()/255f}) );
                    }

                    // Setting inheritable attributes
                    IPdfStructureElement parent = (IPdfStructureElement) this.getParent(true);
                    PdfObject obj = getParentAttribute(parent, PdfName.COLOR);
                    if ((chunk.getFont() != null) && (chunk.getFont().getColor() != null)) {
                        BaseColor c = chunk.getFont().getColor();
                        setColorAttribute(c, obj, PdfName.COLOR);
                    }
                    PdfObject decorThickness  = getParentAttribute(parent, PdfName.TEXTDECORATIONTHICKNESS);
                    PdfObject decorColor  = getParentAttribute(parent, PdfName.TEXTDECORATIONCOLOR);
                    if (attr.containsKey(Chunk.UNDERLINE)){
                        Object[][] unders = (Object[][])attr.get(Chunk.UNDERLINE);
                        Object[] arr = unders[unders.length-1];
                        BaseColor color = (BaseColor)arr[0];
                        float [] floats = (float[]) arr[1];
                        float thickness = floats[0];
                        // Setting thickness
                        if (decorThickness instanceof PdfNumber){
                            float t = ((PdfNumber) decorThickness).floatValue();
                            if (Float.compare(thickness,t) != 0){
                                this.setAttribute(PdfName.TEXTDECORATIONTHICKNESS, new PdfNumber(thickness));
                            }
                        }
                        else
                            this.setAttribute(PdfName.TEXTDECORATIONTHICKNESS, new PdfNumber(thickness));

                        // Setting decoration color
                        if (color != null){
                            setColorAttribute(color, decorColor, PdfName.TEXTDECORATIONCOLOR);
                        }
                    }
                    
                    if (attr.containsKey(Chunk.LINEHEIGHT)){
                        float height = (Float)attr.get(Chunk.LINEHEIGHT);
                        PdfObject parentLH = getParentAttribute(parent, PdfName.LINEHEIGHT);
                        if (parentLH instanceof PdfNumber){
                            float pLH = ((PdfNumber)parentLH).floatValue();
                            if (Float.compare(pLH, height) != 0){
                                this.setAttribute(PdfName.LINEHEIGHT, new PdfNumber(height));
                            }
                        }
                        else
                            this.setAttribute(PdfName.LINEHEIGHT, new PdfNumber(height));
                    }
                }
            }
        }
    }

    private void writeAttributes(final Image image) {
        if (image != null) {
            this.setAttribute(PdfName.O, PdfName.LAYOUT);
            if (image.getWidth() > 0){
                this.setAttribute(PdfName.WIDTH, new PdfNumber(image.getWidth()));
            }
            if (image.getHeight() > 0){
                this.setAttribute(PdfName.HEIGHT, new PdfNumber(image.getHeight()));
            }
            PdfRectangle rect = new PdfRectangle(image, image.getRotation());
            this.setAttribute(PdfName.BBOX, rect);
            if (image.getBackgroundColor() != null){
                BaseColor color = image.getBackgroundColor();
                this.setAttribute(PdfName.BACKGROUNDCOLOR, new PdfArray(new float[] {color.getRed()/255f, color.getGreen()/255f, color.getBlue()/255f}) );
            }
        }
    }

    private void writeAttributes(final PdfTemplate template) {
        if (template != null) {
            this.setAttribute(PdfName.O, PdfName.LAYOUT);
            if (template.getWidth() > 0){
                this.setAttribute(PdfName.WIDTH, new PdfNumber(template.getWidth()));
            }
            if (template.getHeight() > 0){
                this.setAttribute(PdfName.HEIGHT, new PdfNumber(template.getHeight()));
            }
            PdfRectangle rect = new PdfRectangle(template.getBoundingBox());
            this.setAttribute(PdfName.BBOX, rect);
        }
    }

    private void writeAttributes(final Paragraph paragraph) {
        if (paragraph != null) {
            this.setAttribute(PdfName.O, PdfName.LAYOUT);
            // Setting non-inheritable attributes
            if (Float.compare(paragraph.getSpacingBefore(), 0f) != 0)
                this.setAttribute(PdfName.SPACEBEFORE, new PdfNumber(paragraph.getSpacingBefore()));
            if (Float.compare(paragraph.getSpacingAfter(), 0f) != 0)
                this.setAttribute(PdfName.SPACEAFTER, new PdfNumber(paragraph.getSpacingAfter()));

            // Setting inheritable attributes
            IPdfStructureElement parent = (IPdfStructureElement) this.getParent(true);
            PdfObject obj = getParentAttribute(parent, PdfName.COLOR);
            if ((paragraph.getFont() != null) && (paragraph.getFont().getColor() != null)) {
                BaseColor c = paragraph.getFont().getColor();
                setColorAttribute(c, obj, PdfName.COLOR);
            }
            obj = getParentAttribute(parent, PdfName.TEXTINDENT);
            if (Float.compare(paragraph.getFirstLineIndent(), 0f) != 0) {
                boolean writeIndent = true;
                if (obj instanceof PdfNumber){
                    if (Float.compare(((PdfNumber)obj).floatValue(), new Float(paragraph.getFirstLineIndent())) == 0)
                        writeIndent = false;
                }
                if (writeIndent)
                    this.setAttribute(PdfName.TEXTINDENT, new PdfNumber(paragraph.getFirstLineIndent()));
            }
            obj = getParentAttribute(parent, PdfName.STARTINDENT);
            if (obj instanceof PdfNumber) {
                float startIndent = ((PdfNumber) obj).floatValue();
                if (Float.compare(startIndent, paragraph.getIndentationLeft()) != 0)
                    this.setAttribute(PdfName.STARTINDENT, new PdfNumber(paragraph.getIndentationLeft()));
            } else {
                if (Math.abs(paragraph.getIndentationLeft()) > Float.MIN_VALUE)
                    this.setAttribute(PdfName.STARTINDENT, new PdfNumber(paragraph.getIndentationLeft()));
            }

            obj = getParentAttribute(parent, PdfName.ENDINDENT);
            if (obj instanceof PdfNumber) {
                float endIndent = ((PdfNumber) obj).floatValue();
                if (Float.compare(endIndent, paragraph.getIndentationRight()) != 0)
                    this.setAttribute(PdfName.ENDINDENT, new PdfNumber(paragraph.getIndentationRight()));
            } else {
                if (Float.compare(paragraph.getIndentationRight(), 0) != 0)
                    this.setAttribute(PdfName.ENDINDENT, new PdfNumber(paragraph.getIndentationRight()));
            }

            setTextAlignAttribute(paragraph.getAlignment());
        }
    }

    private void writeAttributes(final List list) {
        if (list != null) {
            this.setAttribute(PdfName.O, PdfName.LIST);
            if (list.isAutoindent()) {
                if (list.isNumbered()) {
                    if (list.isLettered()) {
                        if (list.isLowercase())
                            this.setAttribute(PdfName.LISTNUMBERING, PdfName.LOWERROMAN);
                        else
                            this.setAttribute(PdfName.LISTNUMBERING, PdfName.UPPERROMAN);
                    }
                    else {
                        this.setAttribute(PdfName.LISTNUMBERING, PdfName.DECIMAL);
                    }
                }
                else
                    if (list.isLettered()) {
                        if (list.isLowercase())
                            this.setAttribute(PdfName.LISTNUMBERING, PdfName.LOWERALPHA);
                        else
                            this.setAttribute(PdfName.LISTNUMBERING, PdfName.UPPERALPHA);
                    }
            }
            PdfObject obj = getParentAttribute(parent, PdfName.STARTINDENT);
            if (obj instanceof PdfNumber) {
                float startIndent = ((PdfNumber) obj).floatValue();
                if (Float.compare(startIndent, list.getIndentationLeft()) != 0)
                    this.setAttribute(PdfName.STARTINDENT, new PdfNumber(list.getIndentationLeft()));
            } else {
                if (Math.abs(list.getIndentationLeft()) > Float.MIN_VALUE)
                    this.setAttribute(PdfName.STARTINDENT, new PdfNumber(list.getIndentationLeft()));
            }

            obj = getParentAttribute(parent, PdfName.ENDINDENT);
            if (obj instanceof PdfNumber) {
                float endIndent = ((PdfNumber) obj).floatValue();
                if (Float.compare(endIndent, list.getIndentationRight()) != 0)
                    this.setAttribute(PdfName.ENDINDENT, new PdfNumber(list.getIndentationRight()));
            } else {
                if (Float.compare(list.getIndentationRight(), 0) != 0)
                    this.setAttribute(PdfName.ENDINDENT, new PdfNumber(list.getIndentationRight()));
            }
        }
    }

    private void writeAttributes(final ListItem listItem) {
        if (listItem != null) {
            PdfObject obj = getParentAttribute(parent, PdfName.STARTINDENT);
            if (obj instanceof PdfNumber) {
                float startIndent = ((PdfNumber) obj).floatValue();
                if (Float.compare(startIndent, listItem.getIndentationLeft()) != 0)
                    this.setAttribute(PdfName.STARTINDENT, new PdfNumber(listItem.getIndentationLeft()));
            } else {
                if (Math.abs(listItem.getIndentationLeft()) > Float.MIN_VALUE)
                    this.setAttribute(PdfName.STARTINDENT, new PdfNumber(listItem.getIndentationLeft()));
            }

            obj = getParentAttribute(parent, PdfName.ENDINDENT);
            if (obj instanceof PdfNumber) {
                float endIndent = ((PdfNumber) obj).floatValue();
                if (Float.compare(endIndent, listItem.getIndentationRight()) != 0)
                    this.setAttribute(PdfName.ENDINDENT, new PdfNumber(listItem.getIndentationRight()));
            } else {
                if (Float.compare(listItem.getIndentationRight(), 0) != 0)
                    this.setAttribute(PdfName.ENDINDENT, new PdfNumber(listItem.getIndentationRight()));
            }
        }
    }

    private void writeAttributes(final ListBody listBody) {
        if (listBody != null) {

        }
    }

    private void writeAttributes(final ListLabel listLabel) {
        if (listLabel != null) {
            PdfObject obj = getParentAttribute(parent, PdfName.STARTINDENT);
            if (obj instanceof PdfNumber) {
                float startIndent = ((PdfNumber) obj).floatValue();
                if (Float.compare(startIndent, listLabel.getIndentation()) != 0)
                    this.setAttribute(PdfName.STARTINDENT, new PdfNumber(listLabel.getIndentation()));
            } else {
                if (Math.abs(listLabel.getIndentation()) > Float.MIN_VALUE)
                    this.setAttribute(PdfName.STARTINDENT, new PdfNumber(listLabel.getIndentation()));
            }
        }
    }

    private void writeAttributes(final PdfPTable table) {
        if (table != null) {
            this.setAttribute(PdfName.O, PdfName.TABLE);
            // Setting non-inheritable attributes
            if (Float.compare(table.getSpacingBefore(), 0f) != 0)
                this.setAttribute(PdfName.SPACEBEFORE, new PdfNumber(table.getSpacingBefore()));

            if (Float.compare(table.getSpacingAfter(), 0f) != 0)
                this.setAttribute(PdfName.SPACEAFTER, new PdfNumber(table.getSpacingAfter()));

            if (table.getTotalHeight() > 0){
                this.setAttribute(PdfName.HEIGHT, new PdfNumber(table.getTotalHeight()));
            }
            if (table.getTotalWidth() > 0){
                this.setAttribute(PdfName.WIDTH, new PdfNumber(table.getTotalWidth()));
            }
        }
    }

    private void writeAttributes(final PdfPRow row) {
        if (row != null) {
            this.setAttribute(PdfName.O, PdfName.TABLE);
        }
    }

    private void writeAttributes(final PdfPCell cell) {
        if (cell != null) {
            this.setAttribute(PdfName.O, PdfName.TABLE);
            if (cell.getColspan() != 1){
                this.setAttribute(PdfName.COLSPAN, new PdfNumber(cell.getColspan()));
            }
            if (cell.getRowspan() != 1){
                this.setAttribute(PdfName.ROWSPAN, new PdfNumber(cell.getRowspan()));
            }
            if (cell.getHeaders() != null){
                PdfArray headers = new PdfArray();
                ArrayList<PdfPHeaderCell> list = cell.getHeaders();
                for (PdfPHeaderCell header : list){
                    if (header.getName() != null)
                        headers.add(new PdfString(header.getName()));
                }
                if (!headers.isEmpty())
                    this.setAttribute(PdfName.HEADERS, headers);
            }

            if (cell.getCalculatedHeight() > 0){
                this.setAttribute(PdfName.HEIGHT, new PdfNumber(cell.getCalculatedHeight()));
            }

            if (cell.getWidth() > 0){
                this.setAttribute(PdfName.WIDTH, new PdfNumber(cell.getWidth()));
            }

            if (cell.getBackgroundColor() != null){
                BaseColor color = cell.getBackgroundColor();
                this.setAttribute(PdfName.BACKGROUNDCOLOR, new PdfArray(new float[] {color.getRed()/255f, color.getGreen()/255f, color.getBlue()/255f}) );
            }
        }
    }

    private void writeAttributes(final PdfPHeaderCell headerCell) {
        if (headerCell != null) {
            if (headerCell.getScope() != PdfPHeaderCell.NONE){
                switch (headerCell.getScope()){
                    case PdfPHeaderCell.ROW     : this.setAttribute(PdfName.SCOPE, PdfName.ROW); break;
                    case PdfPHeaderCell.COLUMN  : this.setAttribute(PdfName.SCOPE, PdfName.COLUMN); break;
                    case PdfPHeaderCell.BOTH    : this.setAttribute(PdfName.SCOPE, PdfName.BOTH); break;
                }
            }
            if (headerCell.getName() != null)
                this.setAttribute(PdfName.NAME, new PdfName(headerCell.getName()));
            writeAttributes((PdfPCell)headerCell);
        }
    }

    private void writeAttributes(final PdfPTableHeader header) {
        if (header != null) {
            this.setAttribute(PdfName.O, PdfName.TABLE);
        }
    }

    private void writeAttributes(final PdfPTableBody body) {
        if (body != null) {

        }
    }

    private void writeAttributes(final PdfPTableFooter footer) {
        if (footer != null) {

        }
    }

    private void writeAttributes(final PdfDiv div) {
        if (div != null) {
            // Setting non-inheritable attributes
            if (div.getBackgroundColor() != null)
                setColorAttribute(div.getBackgroundColor(), null, PdfName.BACKGROUNDCOLOR);

            // Setting inheritable attributes
            setTextAlignAttribute(div.getTextAlignment());
        }
    }

    private void writeAttributes(final Document document) {
        if (document != null) {

        }
    }
    
    private boolean colorsEqual(PdfArray parentColor, float [] color){
        if (Float.compare(color[0], parentColor.getAsNumber(0).floatValue()) != 0){
            return false;
        }
        if (Float.compare(color[1], parentColor.getAsNumber(1).floatValue()) != 0){
            return false;
        }
        if (Float.compare(color[2], parentColor.getAsNumber(2).floatValue()) != 0){
            return false;
        }
        return true;
    }

    private void setColorAttribute(BaseColor newColor, PdfObject oldColor, PdfName attributeName){
        float [] colorArr = new float[]{newColor.getRed()/255f, newColor.getGreen()/255f, newColor.getBlue()/255f};
        if ((oldColor != null) && (oldColor instanceof PdfArray)){
            PdfArray oldC = (PdfArray)oldColor;
            if (colorsEqual(oldC, colorArr))
            {
                this.setAttribute(attributeName, new PdfArray(colorArr));
            }
            else
                this.setAttribute(attributeName, new PdfArray(colorArr));
        }
        else
            this.setAttribute(attributeName, new PdfArray(colorArr));
    }

    private void setTextAlignAttribute(final int elementAlign){
        PdfName align = null;
        switch (elementAlign) {
            case Element.ALIGN_LEFT:
                align = PdfName.START;
                break;
            case Element.ALIGN_CENTER:
                align = PdfName.CENTER;
                break;
            case Element.ALIGN_RIGHT:
                align = PdfName.END;
                break;
            case Element.ALIGN_JUSTIFIED:
                align = PdfName.JUSTIFY;
                break;
        }
        PdfObject obj = getParentAttribute(parent, PdfName.TEXTALIGN);
        if (obj instanceof PdfName) {
            PdfName textAlign = ((PdfName) obj);
            if (align != null && !textAlign.equals(align))
                this.setAttribute(PdfName.TEXTALIGN, align);
        } else {
            if (align != null && !PdfName.START.equals(align))
                this.setAttribute(PdfName.TEXTALIGN, align);
        }
    }

    @Override
    public void toPdf(final PdfWriter writer, final OutputStream os) throws IOException {
        PdfWriter.checkPdfIsoConformance(writer, PdfIsoKeys.PDFISOKEY_STRUCTELEM, this);
        super.toPdf(writer, os);
    }

    private PdfObject getParentAttribute(IPdfStructureElement parent, PdfName name) {
        if (parent == null)
            return null;
        return parent.getAttribute(name);
    }

    protected void setStructureTreeRoot(PdfStructureTreeRoot root) {
        this.top = root;
    }

    protected void setStructureElementParent(PdfStructureElement parent) {
        this.parent = parent;
    }

    protected AccessibleElementId getElementId() {
        return elementId;
    }
}
