itextpdf-master/xmlworker/src/main/java/com/itextpdf/tool/xml/css/apply/ListStyleTypeCssApplier.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 java.io.IOException;
import java.util.Map;

import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Chunk;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.GreekList;
import com.itextpdf.text.Image;
import com.itextpdf.text.List;
import com.itextpdf.text.RomanList;
import com.itextpdf.text.ZapfDingbatsList;
import com.itextpdf.text.html.HtmlUtilities;
import com.itextpdf.text.log.Level;
import com.itextpdf.text.log.Logger;
import com.itextpdf.text.log.LoggerFactory;
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.FontSizeTranslator;
import com.itextpdf.tool.xml.exceptions.LocaleMessages;
import com.itextpdf.tool.xml.html.CssApplier;
import com.itextpdf.tool.xml.html.HTML;
import com.itextpdf.tool.xml.net.ImageRetrieve;
import com.itextpdf.tool.xml.net.exc.NoImageException;
import com.itextpdf.tool.xml.pipeline.html.HtmlPipelineContext;
import com.itextpdf.tool.xml.pipeline.html.ImageProvider;
import com.itextpdf.tool.xml.pipeline.html.UrlLinkResolver;

/**
 * @author itextpdf.com
 *
 */
public class ListStyleTypeCssApplier implements CssApplier<List> {

	private final CssUtils utils = CssUtils.getInstance();
	private static final Logger LOG = LoggerFactory.getLogger(ListStyleTypeCssApplier.class);

	/**
	 *
	 */
	public ListStyleTypeCssApplier() {
	}

	/**
	 * The ListCssApplier has the capabilities to change the type of the given {@link List} dependable on the css. This
	 * means: <strong>Always replace your list with the returned one and add content to the list after
	 * applying!</strong><br />
	 * note: not implemented list-style-type:armenian, georgian, decimal-leading-zero.
	 *
	 * @param list the list to style
	 * @param t the tag
	 * @param context the context
	 * @return the changed {@link List}
	 */
        public List apply(final List list, final Tag t, final HtmlPipelineContext context) {
            return apply(list, t, null, null, context);
        }
	public List apply(final List list, final Tag t, final MarginMemory memory, final PageSizeContainable psc, final HtmlPipelineContext context) {
		// not implemented: list-style-type:armenian, georgian, decimal-leading-zero.
		float fontSize = FontSizeTranslator.getInstance().getFontSize(t);
		List lst = list;
		Map<String, String> css = t.getCSS();
		String styleType = css.get(CSS.Property.LIST_STYLE_TYPE);
		BaseColor color = HtmlUtilities.decodeColor(css.get(CSS.Property.COLOR));
		if (null == color) color = BaseColor.BLACK;
		if (null != styleType) {
			if (styleType.equalsIgnoreCase(CSS.Value.NONE)) {
				lst.setLettered(false);
				lst.setNumbered(false);
				lst.setListSymbol("");
			} else if (CSS.Value.DECIMAL.equalsIgnoreCase(styleType)) {
				lst = new List(List.ORDERED);
			} else if (CSS.Value.DISC.equalsIgnoreCase(styleType)) {
				lst = new ZapfDingbatsList(108);
                lst.setAutoindent(false);
                lst.setSymbolIndent(7.75f);
                Chunk symbol = lst.getSymbol();
                symbol.setTextRise(1.5f);
                Font font = symbol.getFont();
                font.setSize(4.5f);
                font.setColor(color);
			} else if (CSS.Value.SQUARE.equalsIgnoreCase(styleType)) {
				lst = new ZapfDingbatsList(110);
				shrinkSymbol(lst, fontSize, color);
			} else if (CSS.Value.CIRCLE.equalsIgnoreCase(styleType)) {
				lst = new ZapfDingbatsList(109);
				lst.setAutoindent(false);
                lst.setSymbolIndent(7.75f);
                Chunk symbol = lst.getSymbol();
                symbol.setTextRise(1.5f);
                Font font = symbol.getFont();
                font.setSize(4.5f);
			} else if (CSS.Value.LOWER_ROMAN.equals(styleType)) {
				lst = new RomanList(true, 0);
				synchronizeSymbol(fontSize, lst, color);
				lst.setAutoindent(true);
			} else if (CSS.Value.UPPER_ROMAN.equals(styleType)) {
				lst = new RomanList(false, 0);
				lst.setAutoindent(true);
				synchronizeSymbol(fontSize, lst, color);
			} else if (CSS.Value.LOWER_GREEK.equals(styleType)) {
				lst = new GreekList(true, 0);
				synchronizeSymbol(fontSize, lst, color);
				lst.setAutoindent(true);
			} else if (CSS.Value.UPPER_GREEK.equals(styleType)) {
				lst = new GreekList(false, 0);
				synchronizeSymbol(fontSize, lst, color);
				lst.setAutoindent(true);
			} else if (CSS.Value.LOWER_ALPHA.equals(styleType) || CSS.Value.LOWER_LATIN.equals(styleType)) {
				lst = new List(List.ORDERED, List.ALPHABETICAL);
				synchronizeSymbol(fontSize, lst, color);
				lst.setLowercase(true);
				lst.setAutoindent(true);
			} else if (CSS.Value.UPPER_ALPHA.equals(styleType) || CSS.Value.UPPER_LATIN.equals(styleType)) {
				lst = new List(List.ORDERED, List.ALPHABETICAL);
				synchronizeSymbol(fontSize, lst, color);
				lst.setLowercase(false);
				lst.setAutoindent(true);
			}
		} else if (t.getName().equalsIgnoreCase(HTML.Tag.OL)) {
			lst = new List(List.ORDERED);
			String type = t.getAttributes().get("type");
			if (type != null) {
				if (type.equals("A")) {
					lst.setLettered(true);
				} else if (type.equals("a")) {
					lst.setLettered(true);
					lst.setLowercase(true);
				}
			}
			synchronizeSymbol(fontSize, lst, color);
			lst.setAutoindent(true);
		} else if (t.getName().equalsIgnoreCase(HTML.Tag.UL)) {
			lst = new List(List.UNORDERED);
			shrinkSymbol(lst, fontSize, color);
		}
		if (null != css.get(CSS.Property.LIST_STYLE_IMAGE)
				&& !css.get(CSS.Property.LIST_STYLE_IMAGE).equalsIgnoreCase(CSS.Value.NONE)) {
			lst = new List();
			String url = utils.extractUrl(css.get(CSS.Property.LIST_STYLE_IMAGE));
			try {
				Image img = new ImageRetrieve(context.getResourcesRootPath(), context.getImageProvider()).retrieveImage(url);
				lst.setListSymbol(new Chunk(img, 0, 0, false));
				lst.setSymbolIndent(img.getWidth());
				if (LOG.isLogging(Level.TRACE)) {
					LOG.trace(String.format(LocaleMessages.getInstance().getMessage("html.tag.list"), url));
				}
			} catch (NoImageException e) {
				if (LOG.isLogging(Level.ERROR)) {
					LOG.error(String.format(LocaleMessages.getInstance().getMessage("html.tag.img.failed"), url), e);
				}
				lst = new List(List.UNORDERED);
			}
			lst.setAutoindent(false);
		}
		lst.setAlignindent(false);
		float leftIndent = 0;
		if (null != css.get(CSS.Property.LIST_STYLE_POSITION) && css.get(CSS.Property.LIST_STYLE_POSITION).equalsIgnoreCase(CSS.Value.INSIDE)) {
			leftIndent += 30;
		} else {
			leftIndent += 15;
		}
		leftIndent += css.get(CSS.Property.MARGIN_LEFT)!=null?utils.parseValueToPt(css.get(CSS.Property.MARGIN_LEFT),fontSize):0;
		leftIndent += css.get(CSS.Property.PADDING_LEFT)!=null?utils.parseValueToPt(css.get(CSS.Property.PADDING_LEFT),fontSize):0;
		lst.setIndentationLeft(leftIndent);
        String startAtr = t.getAttributes().get(HTML.Attribute.START);
        if (startAtr != null) {
            try {
                int start = Integer.parseInt(startAtr);
                lst.setFirst(start);
            } catch (NumberFormatException exc) {}

        }
		return lst;
	}

	private void synchronizeSymbol(final float fontSize, final List lst, final BaseColor color) {
		Font font = lst.getSymbol().getFont();
		font.setSize(fontSize);
		font.setColor(color);
		lst.setSymbolIndent(fontSize);
	}

	private void shrinkSymbol(final List lst, final float fontSize, final BaseColor color) {
		lst.setSymbolIndent(12);
		Chunk symbol = lst.getSymbol();
		//symbol.setTextRise(2);
		Font font = symbol.getFont();
		font.setSize(fontSize);
		font.setColor(color);
	}

	/**
	 * Utility method applying style to a list when no ImageProvider is available.
	 * 
	 * @param e the list
	 * @param t the tag
	 * @see ListStyleTypeCssApplier#apply(List, Tag, HtmlPipelineContext)
	 * @return styled element
	 */
	public Element apply(final List e, final Tag t) {
		return apply(e, t, null, null, null);
	}
}
