itextpdf-master/xmlworker/src/main/java/com/itextpdf/tool/xml/css/apply/HtmlCellCssApplier.java
/*
 *
 * This file is part of the iText (R) project.
    Copyright (c) 1998-2022 iText Group NV
 * Authors: Balder Van Camp, Emiel Ackermann, 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.tool.xml.css.apply;

import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Element;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.html.HtmlUtilities;
import com.itextpdf.tool.xml.Tag;
import com.itextpdf.tool.xml.css.CSS;
import com.itextpdf.tool.xml.css.CssUtils;
import com.itextpdf.tool.xml.css.HeightCalculator;
import com.itextpdf.tool.xml.css.WidthCalculator;
import com.itextpdf.tool.xml.html.CssApplier;
import com.itextpdf.tool.xml.html.HTML;
import com.itextpdf.tool.xml.html.pdfelement.HtmlCell;
import com.itextpdf.tool.xml.html.table.CellSpacingEvent;
import com.itextpdf.tool.xml.html.table.Table;
import com.itextpdf.tool.xml.html.table.TableStyleValues;
import com.itextpdf.tool.xml.pipeline.html.HtmlPipelineContext;

import java.util.Map;
import java.util.Map.Entry;

/**
 * @author Emiel Ackermann
 *
 */
public class HtmlCellCssApplier implements CssApplier<HtmlCell> {

    private final CssUtils utils = CssUtils.getInstance();

	/**
	 * Applies css to a HtmlCell
	 * 
	 * @param cell the HtmlCell
	 * @param t the tag with the styles
	 * @param memory current margin memory
	 * @param psc the {@link PageSize} container
	 * @return a styled HtmlCell
	 */
    public HtmlCell apply(final HtmlCell cell, final Tag t, final MarginMemory memory, final PageSizeContainable psc) {
        return apply(cell, t, memory, psc, null);
    }
    public HtmlCell apply(final HtmlCell cell, final Tag t, final MarginMemory memory, final PageSizeContainable psc, final HtmlPipelineContext ctx) {
        Tag row = t.getParent();
        while(row != null && !row.getName().equals(HTML.Tag.TR)){
    		row = row.getParent();
    	}
        Tag table = t.getParent();
        while(table!= null && !table.getName().equals(HTML.Tag.TABLE)){
    		table = table.getParent();
    	}

        final TableStyleValues values = Table.setBorderAttributeForCell(table);
    	Map<String, String> css = t.getCSS();
		String emptyCells = css.get(CSS.Property.EMPTY_CELLS);
		if(null != emptyCells && CSS.Value.HIDE.equalsIgnoreCase(emptyCells) && cell.getCompositeElements() == null) {
			cell.setBorder(Rectangle.NO_BORDER);
		} else {
	    	cell.setVerticalAlignment(Element.ALIGN_MIDDLE); // Default css behavior. Implementation of "vertical-align" style further along.
            String vAlign = null;
            if (t.getAttributes().containsKey(HTML.Attribute.VALIGN)) {
                vAlign = t.getAttributes().get(HTML.Attribute.VALIGN);
            } else if (css.containsKey(HTML.Attribute.VALIGN)) {
                vAlign = css.get(HTML.Attribute.VALIGN);
            } else if (row != null) {
                if (row.getAttributes().containsKey(HTML.Attribute.VALIGN)) {
                    vAlign = row.getAttributes().get(HTML.Attribute.VALIGN);
                } else if (row.getCSS().containsKey(HTML.Attribute.VALIGN)) {
                    vAlign = row.getCSS().get(HTML.Attribute.VALIGN);
                }
            }
            if (vAlign != null) {
                if (vAlign.equalsIgnoreCase(CSS.Value.TOP)) {
                    cell.setVerticalAlignment(Element.ALIGN_TOP);
                } else if (vAlign.equalsIgnoreCase(CSS.Value.BOTTOM)) {
                    cell.setVerticalAlignment(Element.ALIGN_BOTTOM);
                }
            }

            String align = null;
            if (t.getAttributes().containsKey(HTML.Attribute.ALIGN)) {
                align = t.getAttributes().get(HTML.Attribute.ALIGN);
            } else if (css.containsKey(CSS.Property.TEXT_ALIGN)) {
                align = css.get(CSS.Property.TEXT_ALIGN);
            }

            if (align != null) {
                if (align.equalsIgnoreCase(CSS.Value.CENTER)) {
                    cell.setHorizontalAlignment(Element.ALIGN_CENTER);
                } else if (align.equalsIgnoreCase(CSS.Value.RIGHT)) {
                    cell.setHorizontalAlignment(Element.ALIGN_RIGHT);
                } else if (align.equalsIgnoreCase(CSS.Value.JUSTIFY)) {
                    cell.setHorizontalAlignment(Element.ALIGN_JUSTIFIED);
                }
            }

			if(t.getAttributes().get(HTML.Attribute.WIDTH) != null || css.get(HTML.Attribute.WIDTH) != null) {
				cell.setFixedWidth(new WidthCalculator().getWidth(t, memory.getRootTags(), psc.getPageSize().getWidth()));
			}

            HeightCalculator heightCalc = new HeightCalculator();
            Float height = heightCalc.getHeight(t, psc.getPageSize().getHeight());
            if (height == null && row != null) {
                height = heightCalc.getHeight(row, psc.getPageSize().getHeight());
            }
            if (height != null) {
                cell.setMinimumHeight(height);
            }

	        String colspan = t.getAttributes().get(HTML.Attribute.COLSPAN);
	        if (null != colspan) {
	            cell.setColspan(Integer.parseInt(colspan));
	        }
	        String rowspan = t.getAttributes().get(HTML.Attribute.ROWSPAN);
	        if (null != rowspan) {
	            cell.setRowspan(Integer.parseInt(rowspan));
	        }
	    	for (Entry<String, String> entry : css.entrySet()) {
	        	String key = entry.getKey();
				String value = entry.getValue();
				cell.setUseBorderPadding(true);
                if(key.equalsIgnoreCase(CSS.Property.BACKGROUND_COLOR)) {
					values.setBackground(HtmlUtilities.decodeColor(value));
				} else if(key.equalsIgnoreCase(CSS.Property.VERTICAL_ALIGN)) {
					if(value.equalsIgnoreCase(CSS.Value.TOP)) {
						cell.setVerticalAlignment(Element.ALIGN_TOP);
						cell.setPaddingTop(cell.getPaddingTop()+6);
					} else if(value.equalsIgnoreCase(CSS.Value.BOTTOM)) {
						cell.setVerticalAlignment(Element.ALIGN_BOTTOM);
						cell.setPaddingBottom(cell.getPaddingBottom()+6);
					}
				} else if(key.contains(CSS.Property.BORDER)) {
					if(key.contains(CSS.Value.TOP)) {
						setTopOfBorder(cell, key, value, values);
					} else if(key.contains(CSS.Value.BOTTOM)) {
						setBottomOfBorder(cell, key, value, values);
					} else if(key.contains(CSS.Value.LEFT)) {
						setLeftOfBorder(cell, key, value, values);
					} else if(key.contains(CSS.Value.RIGHT)) {
						setRightOfBorder(cell, key, value, values);
					}
				} else if(key.contains(CSS.Property.CELLPADDING) || key.contains(CSS.Property.PADDING)) {
					if(key.contains(CSS.Value.TOP)) {
						cell.setPaddingTop(cell.getPaddingTop()+utils.parsePxInCmMmPcToPt(value));
					} else if(key.contains(CSS.Value.BOTTOM)) {
						cell.setPaddingBottom(cell.getPaddingBottom()+utils.parsePxInCmMmPcToPt(value));
					} else if(key.contains(CSS.Value.LEFT)) {
						cell.setPaddingLeft(cell.getPaddingLeft()+utils.parsePxInCmMmPcToPt(value));
					} else if(key.contains(CSS.Value.RIGHT)) {
						cell.setPaddingRight(cell.getPaddingRight()+utils.parsePxInCmMmPcToPt(value));
					}
				} else if(key.contains(CSS.Property.TEXT_ALIGN)) {
					cell.setHorizontalAlignment(CSS.getElementAlignment(value));
				}
	    	}
	    	cell.setPaddingLeft(cell.getPaddingLeft() + values.getHorBorderSpacing() + values.getBorderWidthLeft());
			cell.setPaddingRight(cell.getPaddingRight() + values.getBorderWidthRight());
	    	cell.setPaddingTop(cell.getPaddingTop() + values.getVerBorderSpacing() + values.getBorderWidthTop());
	    	cell.setPaddingBottom(cell.getPaddingBottom() + values.getBorderWidthBottom());
		}
		cell.setBorder(Rectangle.NO_BORDER);
		cell.setCellEvent(new CellSpacingEvent(values));
		cell.setCellValues(values);
        return cell;
    }

	private void setTopOfBorder(final HtmlCell cell, final String key, final String value, final TableStyleValues values) {
		if(key.contains(CSS.Property.WIDTH)) {
			values.setBorderWidthTop(utils.parsePxInCmMmPcToPt(value));
		}
		if(key.contains(CSS.Property.COLOR)) {
			values.setBorderColorTop(HtmlUtilities.decodeColor(value));
		} else if(values.getBorderColorTop() == null){
			values.setBorderColorTop(BaseColor.BLACK);
		}
		if(key.contains("style")) {
//			If any, which are the border styles in iText? simulate in the borderevent?
			if(values.getBorderWidthTop(false) == null){
				values.setBorderWidthTop(2.25f);
			}
		}
	}
	private void setBottomOfBorder(final HtmlCell cell, final String key, final String value, final TableStyleValues values) {
		if(key.contains(CSS.Property.WIDTH)) {
			values.setBorderWidthBottom(utils.parsePxInCmMmPcToPt(value));
		}
		if(key.contains(CSS.Property.COLOR)) {
			values.setBorderColorBottom(HtmlUtilities.decodeColor(value));
		} else if(values.getBorderColorBottom() == null){
			values.setBorderColorBottom(BaseColor.BLACK);
		}
		if(key.contains("style")) {
//			If any, which are the border styles in iText? simulate in the borderevent?
			if(values.getBorderWidthBottom(false) == null){
				values.setBorderWidthBottom(2.25f);
			}
		}
	}
	private void setLeftOfBorder(final HtmlCell cell, final String key, final String value, final TableStyleValues values) {
		if(key.contains(CSS.Property.WIDTH)) {
			values.setBorderWidthLeft(utils.parsePxInCmMmPcToPt(value));
		}
		if(key.contains(CSS.Property.COLOR)) {
			values.setBorderColorLeft(HtmlUtilities.decodeColor(value));
		} else if(values.getBorderColorLeft() == null){
			values.setBorderColorLeft(BaseColor.BLACK);
		}
		if(key.contains("style")) {
//			If any, which are the border styles in iText? simulate in the borderevent?
			if(values.getBorderWidthLeft(false) == null){
				values.setBorderWidthLeft(2.25f);
			}
		}
	}
	private void setRightOfBorder(final HtmlCell cell, final String key, final String value, final TableStyleValues values) {
		if(key.contains(CSS.Property.WIDTH)) {
			values.setBorderWidthRight(utils.parsePxInCmMmPcToPt(value));
		}
		if(key.contains(CSS.Property.COLOR)) {
			values.setBorderColorRight(HtmlUtilities.decodeColor(value));
		} else if(values.getBorderColorRight() == null){
			values.setBorderColorRight(BaseColor.BLACK);
		}
		if(key.contains("style")) {
//			If any, which are the border styles in iText? simulate in the borderevent?
			if(values.getBorderWidthRight(false) == null){
				values.setBorderWidthRight(2.25f);
			}
		}
	}
}
