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

com.codename1.ui.html.CSSEngine Maven / Gradle / Ivy

There is a newer version: 7.0.164
Show newest version
/*
 * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores
 * CA 94065 USA or visit www.oracle.com if you need additional information or
 * have any questions.
 */
package com.codename1.ui.html;

import com.codename1.ui.Component;
import com.codename1.ui.Container;
import com.codename1.ui.Display;
import com.codename1.ui.Font;
import com.codename1.ui.Label;
import com.codename1.ui.TextArea;
import com.codename1.ui.geom.Dimension;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.layouts.FlowLayout;
import com.codename1.ui.plaf.Border;
import com.codename1.ui.plaf.Style;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

/**
 * This class is responsible for applying CSS directives to an HTMLComponent
 *
 * @author Ofir Leitner
 */
class CSSEngine {

    private static int DEFAULT_3D_BORDER_COLOR = 0x9a9a9a; // default color for outset/inset/ridge/groove

    //int count; //for debugging
    private static CSSEngine instance; // The instance of this singleton class
    private static Hashtable specialKeys; // A hashtable containing all recognized special key strings and their keycodes
    private Hashtable matchingFonts = new Hashtable(); // A hashtable used as a cache for quick find of matching fonts

    /**
     * A list of the attributes that can contain a URL, in order to scan them and update relative URLs to an absolute one
     */
    private static final int[] URL_ATTRIBUTES = {CSSElement.CSS_BACKGROUND_IMAGE,CSSElement.CSS_LIST_STYLE_IMAGE};

    /**
     * Denotes that the selector should be applied to the unselected style of the component
     */
    final static int STYLE_UNSELECTED=1;

    /**
     * Denotes that the selector should be applied to the selected style of the component
     */
    final static int STYLE_SELECTED=2;

    /**
     * Denotes that the selector should be applied to the pressed style of the component
     */
    final static int STYLE_PRESSED=4;

    /**
     * The indentation applied on a list when its 'list-style-position' is 'inside' vs. 'outside'
     */
    private final static int INDENT_LIST_STYLE_POSITION = 15;

    static final String CLIENT_PROPERTY_CSS_CONTENT = "cssContent";

    // The possible values of the 'text-transform' attribute
    private static final int TEXT_TRANSFORM_NONE = 0;
    private static final int TEXT_TRANSFORM_UPPERCASE = 1;
    private static final int TEXT_TRANSFORM_LOWERCASE = 2;
    private static final int TEXT_TRANSFORM_CAPITALIZE = 3;

    // The possible values of the 'text-decoration' attribute
    private static final int TEXT_DECOR_UNDERLINE = 0;
    private static final int TEXT_DECOR_LINETHROUGH = 1;
    private static final int TEXT_DECOR_NONE = 2;
    private static final int TEXT_DECOR_OVERLINE = 3;

    // The possible values of the '-wap-input-required' attribute
    private static final int INPUT_REQUIRED_TRUE = 0;
    private static final int INPUT_REQUIRED_FALSE = 1;

    // The possible values of the 'background-attachment' attribute
    private static final int BG_ATTACHMENT_FIXED = 0;
    private static final int BG_ATTACHMENT_SCROLL = 1;

    // The possible values of the 'white-space' attribute
    private static final int WHITE_SPACE_NORMAL = 0;
    private static final int WHITE_SPACE_PRE = 1;
    private static final int WHITE_SPACE_NOWRAP = 2;

    // The possible values of the 'display' attribute
    private static final int DISPLAY_INLINE=0;
    private static final int DISPLAY_BLOCK=1;
    private static final int DISPLAY_LIST_ITEM=2;
    private static final int DISPLAY_NONE=3;
    private static final int DISPLAY_MARQUEE=4;

    // The possible values of the 'font-variant' attribute
    private static final int FONT_VARIANT_NORMAL=0;
    private static final int FONT_VARIANT_SMALLCAPS=1;

    // The possible values of the 'list-style-position' attribute
    private static final int LIST_STYLE_POSITION_INSIDE = 0;
    private static final int LIST_STYLE_POSITION_OUTSIDE = 1;

    // The possible values of the 'border-style' attribute
    private static final int BORDER_STYLE_NONE = 0;
    private static final int BORDER_STYLE_SOLID = 1;
    private static final int BORDER_STYLE_DOTTED = 2;
    private static final int BORDER_STYLE_DASHED = 3;
    private static final int BORDER_STYLE_DOUBLE = 4;
    private static final int BORDER_STYLE_GROOVE = 5;
    private static final int BORDER_STYLE_RIDGE = 6;
    private static final int BORDER_STYLE_INSET = 7;
    private static final int BORDER_STYLE_OUTSET = 8;

    private static final int[][] BORDER_OUTLINE_PROPERTIES = {
        {CSSElement.CSS_BORDER_TOP_WIDTH,CSSElement.CSS_BORDER_TOP_STYLE,CSSElement.CSS_BORDER_TOP_COLOR},
        {CSSElement.CSS_OUTLINE_WIDTH,CSSElement.CSS_OUTLINE_STYLE,CSSElement.CSS_OUTLINE_COLOR}
    };

    private static final int BORDER = 0;
    private static final int OUTLINE = 1;

    private static final int WIDTH = 0;
    private static final int STYLE = 1;
    private static final int COLOR = 2;


    // The possible values of the 'visibility' attribute
    private static final int VISIBILITY_HIDDEN=0;
    private static final int VISIBILITY_VISIBLE=1;
    private static final int VISIBILITY_COLLAPSE=2; // collapse behaves the same as hidden in most browsers.

    // The possible values of the 'border-collapse' attribute
    private static final int BORDER_COLLAPSE_COLLAPSE = 0;
    private static final int BORDER_COLLAPSE_SEPARATE = 1;

    // The possible values of the 'empty-cells' attribute
    private static final int EMPTY_CELLS_HIDE = 0;
    private static final int EMPTY_CELLS_SHOW = 1;

    // The possible values of the 'caption-side' attribute
    private static final int CAPTION_SIDE_BOTTOM = 0;
    private static final int CAPTION_SIDE_TOP = 1;

    // The possible values of the 'direction' attribute
    private static final int DIRECTION_RTL = 0;
    private static final int DIRECTION_LTR = 1;
    
    /**
     * Returns the singleton instance of CSSEngine and creates it if necessary
     *
     * @return The singleton instance of CSSEngine
     */
    static CSSEngine getInstance() {
        if (instance==null) {
            instance=new CSSEngine();
        }
        return instance;
    }

    /**
     * Adds support for a special key to be used as an accesskey.
     * The CSS property -wap-accesskey supports special keys, for example "phone-send" that may have different key codes per device.
     * This method allows pairing between such keys to their respective key codes.
     * Note that these keys are valid only for -wap-aceesskey in CSS files, and not for the XHTML accesskey attribute.
     *
     * @param specialKeyName The name of the special key as denoted in CSS files
     * @param specialKeyCode The special key code
     */
    static void addSpecialKey(String specialKeyName,int specialKeyCode) {
        if (specialKeys==null) {
            specialKeys=new Hashtable();
        }
        specialKeys.put(specialKeyName,new Integer(specialKeyCode));
    }

    /**
     * Sorts the CSS directives by their specificity level
     * 
     * @param css A css vector holding CSSElements, where each element holds CSS selectors as its children
     * @return a flat vector containing CSS selectors, sorted by specificity
     */
    private CSSElement[] sortSelectorsBySpecificity(CSSElement[] css) {
        Vector sortedSelectors=new Vector();

        for(int s=0;s=((CSSElement)sortedSelectors.elementAt(i)).getSelectorSpecificity())) {
                    i++;
                }
                sortedSelectors.insertElementAt(currentSelector, i);
            }
        }

        css = new CSSElement[sortedSelectors.size()];
        for(int i=0;i0) && !(element.getUi().firstElement() instanceof HTMLLink)) ||
                        ((element.getUi().size()>0) && (!((HTMLLink)element.getUi().firstElement()).linkVisited) && ((currentSelector.getSelectorPseudoClass() & CSSElement.PC_LINK)!=0)) ||
                        ((element.getUi().size()>0) && ((HTMLLink)element.getUi().firstElement()).linkVisited) && ((currentSelector.getSelectorPseudoClass() & CSSElement.PC_VISITED)!=0)) {
                        applyStyle(element, currentSelector,htmlC);
                    }
                } else {
                    CSSElement child=currentSelector.getCSSChildAt(0);
                    if (child.siblingSelector) {
                        if (!HTMLComponent.PROCESS_HTML_MP1_ONLY) { // sibling selectors are not supported in HTML-MP1
                            nextSiblingSelectors.addElement(child);
                        }
                    } else {
                        nextNestedSelectors.addElement(child);
                        // Check if this is a Descendant selector (i.e. div b - which means match any b that is the descendant of div
                        // If so then we pass not only the child selector (i.e. the b) but also the "* b" to allow matching later decendants
                        if (child.descendantSelector) {
                            CSSElement elem=new CSSElement("*");
                            elem.addChild(new CSSElement(child));
                            nextNestedSelectors.addElement(elem);
                        }
                    }
                }
        }
    }

    /**
     * Checks if the specified class is contained in the specified text
     * This is used for elements that have several classes i.e. class="class1 class2"
     * Note: A simple indexOf could not be used since we need to find whole words and not frgaments of words
     *
     * @param selectorClass The text
     * @param elementClass The word to find in the text
     * @return true if the word is found, false otherwise
     */
    private boolean containsClass(String elementClass,String selectorClass) {
        if ((elementClass==null) || (selectorClass==null)) {
            return false;
        }
        // The spaces addition is to make sure we get a whole word and not a fragment of a word
        elementClass=" "+elementClass+" ";
        
        // Selector can require multiple classes, i.e. class.1class2 (which needs to match to "class 1 class2" and "class2 class1" and also "class1 otherclasses class2"
        int dotIndex=selectorClass.indexOf('.');
        while (dotIndex!=-1) {
            String curWord=selectorClass.substring(0, dotIndex);
            if (elementClass.indexOf(" "+curWord+" ")==-1) {
                return false;
            }
            selectorClass=selectorClass.substring(dotIndex+1);
            dotIndex=selectorClass.indexOf('.');
        }

        return (elementClass.indexOf(" "+selectorClass+" ")!=-1);
    }

    /**
     * Applies the given style attributes to the HTML DOM entry
     *
     * @param element The element to apply the style to
     * @param selector The selector containing the style directives
     * @param htmlC The HTMLComponent
     */
    private void applyStyle(HTMLElement element, CSSElement selector, HTMLComponent htmlC) {
        if ((element.getUi() != null) && (element.getUi().size()>0)) {
            if (!HTMLComponent.PROCESS_HTML_MP1_ONLY) {
                String reset=selector.getAttributeById(CSSElement.CSS_COUNTER_RESET);
                if (reset!=null) {
                    htmlC.incCounter(reset, true);
                }
                String inc=selector.getAttributeById(CSSElement.CSS_COUNTER_INCREMENT);
                if (inc!=null) {
                    htmlC.incCounter(inc, false);
                }

                if ((selector.getSelectorPseudoClass() & (CSSElement.PC_BEFORE|CSSElement.PC_AFTER))!=0) {
                    handleContentProperty(element,selector,htmlC);
                    return;
                }
            }
            for(int iter = 0 ; iter < element.getUi().size() ; iter++) {
                Object o = element.getUi().elementAt(iter);
                if(o != null && o instanceof Component) {
                    final Component cmp = (Component)o;
                    applyStyleToUIElement(cmp, selector,element,htmlC);                        
                }
            }
        }
    }

    /**
     * Returns a mask of the STYLE_* constants of which CodenameOne styles this selector should be applied to
     *
     * @param cmp The component in question
     * @param selector The selector
     * @return a mask of the STYLE_* constants of which CodenameOne styles this selector should be applied to
     */
    private int getApplicableStyles(Component cmp,CSSElement selector) {
        int result=0;
        if (cmp instanceof HTMLLink) {
            int pseudoClass=selector.getSelectorPseudoClass();
            boolean done=false;
            if ((pseudoClass & CSSElement.PC_FOCUS)!=0) { // Focused (i.e. CSS focus/hover)
                result|=STYLE_SELECTED;
                done=true;
            }
            if ((pseudoClass & CSSElement.PC_ACTIVE)!=0) { // active in CSS means pressed in CodenameOne
                result|=STYLE_PRESSED;
                done=true;
            }

            if (!done) {
                result|=STYLE_SELECTED|STYLE_UNSELECTED;
            }
        } else {
                result|=STYLE_SELECTED|STYLE_UNSELECTED;
        }
        return result;
    }

    /**
     * Sets the specified color as the foreground color of the component and all its children
     * 
     * @param cmp The component to work on
     * @param color The color to set
     * @param selector The selector with the color directive
     */
    private void setColorRecursive(Component cmp,int color,CSSElement selector) {
        int styles=getApplicableStyles(cmp, selector);
        if ((styles & STYLE_UNSELECTED)!=0) {
            cmp.getUnselectedStyle().setFgColor(color);
        }
        if ((styles & STYLE_SELECTED)!=0) {
            cmp.getSelectedStyle().setFgColor(color);
        }
        if ((styles & STYLE_PRESSED)!=0) {
            ((HTMLLink)cmp).getPressedStyle().setFgColor(color);
        }

        if (cmp instanceof Container) {
            Container cont=(Container)cmp;
            for(int i=0;i='a') && (c<='z')) {
                                c-=32; // 'A' is ASCII 65, and 'a' is ASCII 97, difference: 32
                            }
                            capNextLetter=false;
                        }
                        newText+=c;
                    }
                    label.setText(newText);
                    break;
            }
        }

    }

    /**
     * Sets the alignment of the component and all its children according to the given alignment
     * 
     * @param cmp The component to set the alignment on
     * @param align The alignment - one of left,center,right
     */
    private void setTextAlignmentRecursive(Component cmp,int align) {
        if (cmp instanceof Container) {
            Container cont=(Container)cmp;
            if (cont.getLayout() instanceof FlowLayout) {
                cont.setLayout(new FlowLayout(align));
            }
            for(int i=0;i0)) {
                // Note that we don't need to consider the "applicable" styles, as this is a container and will always return selected+unselected
                cont.getComponentAt(0).getUnselectedStyle().setMargin(Component.LEFT, indent);
                cont.getComponentAt(0).getSelectedStyle().setMargin(Component.LEFT, indent);
            }
            for(int i=0;i1)) { //If it's just one word or already no-wrapped, no need to process
                String word="";
                String newText="";
                for(int c=0;c




© 2015 - 2024 Weber Informatics LLC | Privacy Policy