All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.itextpdf.styledxmlparser.css.util.CssUtils Maven / Gradle / Ivy

There is a newer version: 8.0.5
Show newest version
/*
    This file is part of the iText (R) project.
    Copyright (c) 1998-2023 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: [email protected]
 */
package com.itextpdf.styledxmlparser.css.util;

import com.itextpdf.layout.font.RangeBuilder;
import com.itextpdf.styledxmlparser.CommonAttributeConstants;
import com.itextpdf.styledxmlparser.node.IElementNode;
import com.itextpdf.layout.font.Range;
import com.itextpdf.layout.properties.BlendMode;
import com.itextpdf.styledxmlparser.css.CommonCssConstants;
import com.itextpdf.styledxmlparser.css.parse.CssDeclarationValueTokenizer;
import com.itextpdf.styledxmlparser.css.parse.CssDeclarationValueTokenizer.Token;
import com.itextpdf.styledxmlparser.css.parse.CssDeclarationValueTokenizer.TokenType;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Utilities class for CSS operations.
 */
public class CssUtils {
    private static final float EPSILON = 1e-6f;

    private static final  Logger logger = LoggerFactory.getLogger(CssUtils.class);

    private static final int QUANTITY_OF_PARAMS_WITH_FALLBACK_OR_TYPE = 2;

    /**
     * Creates a new {@link CssUtils} instance.
     */
    private CssUtils() {
        // Empty constructor
    }

    /**
     * Splits the provided {@link String} by comma with respect of brackets.
     *
     * @param value to split
     * @return the {@link List} of split result
     */
    public static List splitStringWithComma(final String value) {
        return splitString(value, ',', new EscapeGroup('(', ')'));
    }

    /**
     * Splits the provided {@link String} by split character with respect of escape characters.
     *
     * @param value value to split
     * @param splitChar character to split the String
     * @param escapeCharacters escape characters
     * @return the {@link List} of split result
     */
    public static List splitString(String value, char splitChar, EscapeGroup... escapeCharacters) {
        if (value == null) {
            return new ArrayList<>();
        }
        final List resultList = new ArrayList<>();
        int lastSplitChar = 0;
        for (int i = 0; i < value.length(); ++i) {
            final char currentChar = value.charAt(i);
            boolean isEscaped = false;
            for (final EscapeGroup character : escapeCharacters) {
                if (currentChar == splitChar) {
                    isEscaped = isEscaped || character.isEscaped();
                } else {
                    character.processCharacter(currentChar);
                }
            }
            if (currentChar == splitChar && !isEscaped) {
                resultList.add(value.substring(lastSplitChar, i));
                lastSplitChar = i + 1;
            }
        }
        final String lastToken = value.substring(lastSplitChar);
        if (!lastToken.isEmpty()) {
            resultList.add(lastToken);
        }
        return resultList;
    }

    /**
     * Parses the given css blend mode value. If the argument is {@code null} or an unknown blend
     * mode, then the default css {@link BlendMode#NORMAL} value would be returned.
     *
     * @param cssValue the value to parse
     * @return the {@link BlendMode} instance representing the parsed value
     */
    public static BlendMode parseBlendMode(String cssValue) {
        if (cssValue == null) {
            return BlendMode.NORMAL;
        }

        switch (cssValue) {
            case CommonCssConstants.MULTIPLY:
                return BlendMode.MULTIPLY;
            case CommonCssConstants.SCREEN:
                return BlendMode.SCREEN;
            case CommonCssConstants.OVERLAY:
                return BlendMode.OVERLAY;
            case CommonCssConstants.DARKEN:
                return BlendMode.DARKEN;
            case CommonCssConstants.LIGHTEN:
                return BlendMode.LIGHTEN;
            case CommonCssConstants.COLOR_DODGE:
                return BlendMode.COLOR_DODGE;
            case CommonCssConstants.COLOR_BURN:
                return BlendMode.COLOR_BURN;
            case CommonCssConstants.HARD_LIGHT:
                return BlendMode.HARD_LIGHT;
            case CommonCssConstants.SOFT_LIGHT:
                return BlendMode.SOFT_LIGHT;
            case CommonCssConstants.DIFFERENCE:
                return BlendMode.DIFFERENCE;
            case CommonCssConstants.EXCLUSION:
                return BlendMode.EXCLUSION;
            case CommonCssConstants.HUE:
                return BlendMode.HUE;
            case CommonCssConstants.SATURATION:
                return BlendMode.SATURATION;
            case CommonCssConstants.COLOR:
                return BlendMode.COLOR;
            case CommonCssConstants.LUMINOSITY:
                return BlendMode.LUMINOSITY;
            case CommonCssConstants.NORMAL:
            default:
                return BlendMode.NORMAL;
        }
    }

    /**
     * Extracts shorthand properties as list of string lists from a string, where the top level
     * list is shorthand property and the lower level list is properties included in shorthand property.
     *
     * @param str the source string with shorthand properties
     * @return the list of string lists
     */
    public static List> extractShorthandProperties(String str) {
        List> result = new ArrayList<>();
        List currentLayer = new ArrayList<>();
        CssDeclarationValueTokenizer tokenizer = new CssDeclarationValueTokenizer(str);

        Token currentToken = tokenizer.getNextValidToken();
        while (currentToken != null) {
            if (currentToken.getType() == TokenType.COMMA) {
                result.add(currentLayer);
                currentLayer = new ArrayList<>();
            } else {
                currentLayer.add(currentToken.getValue());
            }
            currentToken = tokenizer.getNextValidToken();
        }
        result.add(currentLayer);

        return result;
    }

    /**
     * Normalizes a CSS property.
     *
     * @param str the property
     * @return the normalized property
     */
    public static String normalizeCssProperty(String str) {
        return str == null ? null : CssPropertyNormalizer.normalize(str);
    }

    /**
     * Removes double spaces and trims a string.
     *
     * @param str the string
     * @return the string without the unnecessary spaces
     */
    public static String removeDoubleSpacesAndTrim(String str) {
        String[] parts = str.split("\\s");
        StringBuilder sb = new StringBuilder();
        for (String part : parts) {
            if (part.length() > 0) {
                if (sb.length() != 0) {
                    sb.append(" ");
                }
                sb.append(part);
            }
        }
        return sb.toString();
    }

    /**
     * Parses {@code url("file.jpg")} to {@code file.jpg}.
     *
     * @param url the url attribute to parse
     * @return the parsed url. Or original url if not wrappend in url()
     */
    public static String extractUrl(final String url) {
        String str = null;
        if (url.startsWith("url")) {
            String urlString = url.substring(3).trim().replace("(", "").replace(")", "").trim();
            if (urlString.startsWith("'") && urlString.endsWith("'")) {
                str = urlString.substring(urlString.indexOf("'") + 1, urlString.lastIndexOf("'"));
            } else if (urlString.startsWith("\"") && urlString.endsWith("\"")) {
                str = urlString.substring(urlString.indexOf('"') + 1, urlString.lastIndexOf('"'));
            } else {
                str = urlString;
            }
        } else {
            // assume it's an url without wrapping in "url()"
            str = url;
        }
        return str;
    }

    /**
     * Parses string and return attribute value.
     *
     * @param attrStr the string contains attr() to extract attribute value
     * @param element the parentNode from which we extract information
     * @return the value of attribute
     */
    public static String extractAttributeValue(final String attrStr, IElementNode element) {
        String attrValue = null;
        if (attrStr.startsWith(CommonCssConstants.ATTRIBUTE + '(')
                && attrStr.length() > CommonCssConstants.ATTRIBUTE.length() + 2 && attrStr.endsWith(")")) {
            String fallback = null;
            String typeOfAttribute = null;
            final String stringToSplit = attrStr.substring(5, attrStr.length() - 1);
            final List paramsWithFallback = splitString(stringToSplit, ',', new EscapeGroup('\"'),
                    new EscapeGroup('\''));
            if (paramsWithFallback.size() > QUANTITY_OF_PARAMS_WITH_FALLBACK_OR_TYPE) {
                return null;
            }
            if (paramsWithFallback.size() == QUANTITY_OF_PARAMS_WITH_FALLBACK_OR_TYPE) {
                fallback = extractFallback(paramsWithFallback.get(1));
            }
            final List params = splitString(paramsWithFallback.get(0), ' ');
            if (params.size() > QUANTITY_OF_PARAMS_WITH_FALLBACK_OR_TYPE) {
                return null;
            }
            if (params.size() == QUANTITY_OF_PARAMS_WITH_FALLBACK_OR_TYPE) {
                typeOfAttribute = extractTypeOfAttribute(params.get(1));
                if (typeOfAttribute == null) {
                    return null;
                }
            }
            String attributeName = params.get(0);
            if (isAttributeNameValid(attributeName)) {
                attrValue = getAttributeValue(attributeName, typeOfAttribute, fallback, element);
            }
        }
        return attrValue;
    }
    /**
     * Find the next unescaped character.
     *
     * @param source     a source
     * @param ch         the character to look for
     * @param startIndex where to start looking
     * @return the position of the next unescaped character
     */
    public static int findNextUnescapedChar(String source, char ch, int startIndex) {
        int symbolPos = source.indexOf(ch, startIndex);
        if (symbolPos == -1) {
            return -1;
        }
        int afterNoneEscapePos = symbolPos;
        while (afterNoneEscapePos > 0 && source.charAt(afterNoneEscapePos - 1) == '\\') {
            --afterNoneEscapePos;
        }
        return (symbolPos - afterNoneEscapePos) % 2 == 0 ? symbolPos : findNextUnescapedChar(source, ch, symbolPos + 1);
    }

    /**
     * Helper method for comparing floating point numbers
     *
     * @param d1 first float to compare
     * @param d2 second float to compare
     * @return True if both floats are equal within a Epsilon defined in this class, false otherwise
     */
    public static boolean compareFloats(double d1, double d2) {
        return (Math.abs(d1 - d2) < EPSILON);
    }

    /**
     * Helper method for comparing floating point numbers
     *
     * @param f1 first float to compare
     * @param f2 second float to compare
     * @return True if both floats are equal within a Epsilon defined in this class, false otherwise
     */
    public static boolean compareFloats(float f1, float f2) {
        return (Math.abs(f1 - f2) < EPSILON);
    }

    /**
     * Parses the unicode range.
     *
     * @param unicodeRange the string which stores the unicode range
     * @return the unicode range as a {@link Range} object
     */
    public static Range parseUnicodeRange(String unicodeRange) {
        String[] ranges = unicodeRange.split(",");
        RangeBuilder builder = new RangeBuilder();
        for (String range : ranges) {
            if (!addRange(builder, range)) {
                return null;
            }
        }
        return builder.create();
    }

    /**
     * Convert given point value to a pixel value. 1 px is 0.75 pts.
     *
     * @param pts float value to be converted to pixels
     * @return float converted value pts/0.75f
     */
    public static float convertPtsToPx(float pts) {
        return pts / 0.75f;
    }

    /**
     * Convert given point value to a pixel value. 1 px is 0.75 pts.
     *
     * @param pts double value to be converted to pixels
     * @return double converted value pts/0.75
     */
    public static double convertPtsToPx(double pts) {
        return pts / 0.75;
    }

    /**
     * Convert given point value to a point value. 1 px is 0.75 pts.
     *
     * @param px float value to be converted to pixels
     * @return float converted value px*0.75
     */
    public static float convertPxToPts(float px) {
        return px * 0.75f;
    }

    /**
     * Convert given point value to a point value. 1 px is 0.75 pts.
     *
     * @param px double value to be converted to pixels
     * @return double converted value px*0.75
     */
    public static double convertPxToPts(double px) {
        return px * 0.75;
    }

    /**
     * Checks if an {@link IElementNode} represents a style sheet link.
     *
     * @param headChildElement the head child element
     * @return true, if the element node represents a style sheet link
     */
    public static boolean isStyleSheetLink(IElementNode headChildElement) {
        return CommonCssConstants.LINK.equals(headChildElement.name())
                && CommonAttributeConstants.STYLESHEET
                .equals(headChildElement.getAttribute(CommonAttributeConstants.REL));
    }

    private static boolean addRange(RangeBuilder builder, String range) {
        range = range.trim();
        if (range.matches("[uU]\\+[0-9a-fA-F?]{1,6}(-[0-9a-fA-F]{1,6})?")) {
            String[] parts = range.substring(2, range.length()).split("-");
            if (1 == parts.length) {
                if (parts[0].contains("?")) {
                    return addRange(builder, parts[0].replace('?', '0'), parts[0].replace('?', 'F'));
                } else {
                    return addRange(builder, parts[0], parts[0]);
                }
            } else {
                return addRange(builder, parts[0], parts[1]);
            }
        }
        return false;
    }

    private static boolean addRange(RangeBuilder builder, String left, String right) {
        int l = Integer.parseInt(left, 16);
        int r = Integer.parseInt(right, 16);
        if (l > r || r > 1114111) {
            // Although Firefox follows the spec (and therefore the second condition), it seems it's ignored in Chrome or Edge
            return false;
        }
        builder.addRange(l, r);
        return true;
    }

    private static boolean isAttributeNameValid(String attributeName) {
        return !(attributeName.contains("'") || attributeName.contains("\"") || attributeName.contains("(")
                || attributeName.contains(")"));
    }

    private static String extractFallback(String fallbackString) {
        String tmpString;
        if ((fallbackString.startsWith("'") && fallbackString.endsWith("'")) || (fallbackString.startsWith("\"")
                && fallbackString.endsWith("\""))) {
            tmpString = fallbackString.substring(1, fallbackString.length() - 1);
        } else {
            tmpString = fallbackString;
        }
        return extractUrl(tmpString);
    }

    private static String extractTypeOfAttribute(String typeString) {
        if (typeString.equals(CommonCssConstants.URL) || typeString.equals(CommonCssConstants.STRING)) {
            return typeString;
        }
        return null;
    }

    private static String getAttributeValue(final String attributeName, final String typeOfAttribute,
            final String fallback,
            IElementNode elementNode) {
        String returnString = elementNode.getAttribute(attributeName);
        if (CommonCssConstants.URL.equals(typeOfAttribute)) {
            returnString = returnString == null ? null : extractUrl(returnString);
        } else {
            returnString = returnString == null ? "" : returnString;
        }
        if (fallback != null && (returnString == null || returnString.isEmpty())) {
            returnString = fallback;
        }
        return returnString;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy