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

org.fit.cssbox.layout.VisualContext Maven / Gradle / Ivy

Go to download

CSSBox is an (X)HTML/CSS rendering engine written in pure Java. Its primary purpose is to provide a complete information about the rendered page suitable for further processing. However, it also allows displaying the rendered document.

There is a newer version: 5.0.2
Show newest version
/*
 * VisualContext.java
 * Copyright (c) 2005-2014 Radek Burget
 *
 * CSSBox is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *  
 * CSSBox 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 Lesser General Public License for more details.
 *  
 * You should have received a copy of the GNU Lesser General Public License
 * along with CSSBox. If not, see .
 *
 * Created on 7. z��� 2005, 15:33
 */

package org.fit.cssbox.layout;

import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.util.ArrayList;
import java.util.List;

import org.fit.cssbox.css.CSSUnits;

import cz.vutbr.web.css.*;
import cz.vutbr.web.css.CSSProperty.FontFamily;
import cz.vutbr.web.css.CSSProperty.TextDecoration;

/**
 * The visual context represents the context of the element - the current font properties, EM and EX values,
 * font metrics and color.
 *
 * @author  burgetr
 */
public class VisualContext 
{
    private VisualContext parent;
    private VisualContext rootContext; //the visual context of the root element
    private BoxFactory factory; //the factory used for obtaining current configuration
    private Viewport viewport; //the viewport used for obtaining the vw sizes
    private Font font; //current font
    private FontMetrics fm; //current font metrics
    private double fontSize;
    private CSSProperty.FontWeight fontWeight;
    private CSSProperty.FontStyle fontStyle;
    private CSSProperty.FontVariant fontVariant;
    private List textDecoration;
    private double em; //number of pixels in 1em
    private double rem; //number of pixels in 1rem 
    private double ex; //number of pixels in 1ex
    private double ch; //number of pixels in 1ch
    private double dpi; //number of pixels in 1 inch
    
    public Color color; //current text color
    
    public VisualContext(VisualContext parent, BoxFactory factory)
    {
        this.parent = parent;
        this.factory = factory;
        rootContext = (parent == null) ? this : parent.rootContext;
        em = CSSUnits.medium_font;
        rem = em;
        ex = 0.6 * em;
        ch = 0.8 * ch; //just an initial guess, updated in updateForGraphics()
        dpi = org.fit.cssbox.css.CSSUnits.dpi;
        font = new Font(Font.SERIF, Font.PLAIN, (int) CSSUnits.medium_font);
        fontSize = CSSUnits.points(CSSUnits.medium_font);
        fontWeight = CSSProperty.FontWeight.NORMAL;
        fontStyle = CSSProperty.FontStyle.NORMAL;
        fontVariant = CSSProperty.FontVariant.NORMAL;
        textDecoration = new ArrayList(2); //it is not very probable to have more than two decorations
        color = Color.BLACK;
    }
    
    public VisualContext create()
    {
        VisualContext ret = new VisualContext(this, this.factory);
        ret.viewport = viewport;
        ret.rootContext = rootContext;
        ret.em = em;
        ret.rem = rem;
        ret.ex = ex;
        ret.ch = ch;
        ret.dpi = dpi;
        ret.font = font;
        ret.fontSize = fontSize;
        ret.fontWeight = fontWeight;
        ret.fontStyle = fontStyle;
        ret.fontVariant = fontVariant;
        ret.textDecoration = new ArrayList(textDecoration);
        ret.color = color;
        return ret;
    }
   
    //=========================================================================
    
    public VisualContext getParentContext()
    {
        return parent;
    }
    
    public Viewport getViewport()
    {
        return viewport;
    }

    public void setViewport(Viewport viewport)
    {
        this.viewport = viewport;
    }

    public boolean isRootContext()
    {
        return (this == rootContext);
    }

    public void makeRootContext()
    {
        if (this.rootContext != null)
            this.rootContext.rootContext = this; //the old root now points to us
        this.rootContext = this; //we also point to us
    }

    /**
     * The AWT font used for the box.
     * @return current font
     */
    public Font getFont()
    {
        return font;
    }

    /**
     * Obtains the specified font size in pt.
     * @return the font size in pt
     */
    public double getFontSize()
    {
        return fontSize;
    }
    
    /**
     * The font variant used for the box.
     * @return normal or small-caps
     */
    public String getFontVariant()
    {
        return fontVariant.toString();
    }
    
    /**
     * Returns the text decoration used for the box.
     * @return none or a string of space separated keywords underline, overline, line-through or blink
     */
    public String getTextDecorationString()
    {
        if (textDecoration.isEmpty())
            return "none";
        else
        {
            StringBuilder ret = new StringBuilder();
            for (CSSProperty.TextDecoration dec : textDecoration)
            {
                if (ret.length() > 0)
                    ret.append(' ');
                ret.append(dec.toString());
            }
            return ret.toString();
        }
    }
    
    /**
     * Returns the text decoration used for the box.
     * @return a list of TextDecoration values
     */
    public List getTextDecoration()
    {
        return textDecoration;
    }
    
    /**
     * The text color used for the box.
     * @return color specification
     */
    public Color getColor()
    {
        return color;
    }
    
    /**
     * @return the em value of the context
     */
    public double getEm()
    {
        return em;
    }

    /**
     * @return the rem value of the context
     */
    public double getRem()
    {
        return rem;
    }

    /**
     * @return the ex value of the context
     */
    public double getEx()
    {
        return ex;
    }

    /**
     * @return the 'ch' value of the context
     */
    public double getCh()
    {
        return ch;
    }
    
    /**
     * @return the dpi value used in the context
     */
    public double getDpi()
    {
        return dpi;
    }

    //=========================================================================
    
    /** 
     * Updates the context according to the given element style. The properties that are not defined 
     * in the style are left unchanged.
     * @param style the style data 
     */
    public void update(NodeData style)
    {
        //setup the font
        String family = null;
        CSSProperty.FontFamily ff = style.getProperty("font-family");
        if (ff == null)
            family = font.getFamily(); //use current
        else if (ff == FontFamily.list_values)
        {
            TermList fmlspec = style.getValue(TermList.class, "font-family");
            if (fmlspec == null)
                family = font.getFamily();
            else
                family = getFontName(fmlspec);
        }
        else
        {
            if (factory != null)
                family = factory.getConfig().getDefaultFont(ff.getAWTValue()); //try to translate to physical font
            if (family == null)
                family = ff.getAWTValue(); //could not translate - use as is
        }
        
        double size;
        double psize = (parent == null) ? CSSUnits.medium_font : parent.getEm();
        CSSProperty.FontSize fsize = style.getProperty("font-size");
        if (fsize == null)
            size = em;
        else if (fsize == CSSProperty.FontSize.length || fsize == CSSProperty.FontSize.percentage)
        {
            TermLengthOrPercent lenspec = style.getValue(TermLengthOrPercent.class, "font-size");
            if (lenspec != null)
            {
                em = psize;
                size = pxLength(lenspec, psize); //pixels are ok here (java is fixed to 72 dpi for font sizes)
            }
            else
                size = em;
        }
        else
            size = CSSUnits.convertFontSize(psize, fsize);
        fontSize = CSSUnits.points(size);
        
        if (rootContext != null)
            rem = rootContext.getEm();
        else
            rem = em; //we don't have a root context?
        
        CSSProperty.FontWeight weight = style.getProperty("font-weight");
        if (weight != null) fontWeight = weight;
        CSSProperty.FontStyle fstyle =  style.getProperty("font-style");
        if (fstyle != null) fontStyle = fstyle;
        int fs = Font.PLAIN;
        if (representsBold(fontWeight))
            fs = Font.BOLD;
        if (fontStyle == CSSProperty.FontStyle.ITALIC || fontStyle == CSSProperty.FontStyle.OBLIQUE)
            fs = fs | Font.ITALIC;
        
        font = new Font(family, fs, (int) Math.round(size));
        em = size;
        
        CSSProperty.FontVariant variant = style.getProperty("font-variant");
        if (variant != null) fontVariant = variant;
        CSSProperty.TextDecoration decor = style.getProperty("text-decoration");
        textDecoration.clear();
        if (decor != null)
        {
            if (decor == TextDecoration.list_values)
            {
                TermList list = style.getValue(TermList.class, "text-decoration");
                for (Term t : list)
                {
                    if (t.getValue() instanceof CSSProperty.TextDecoration)
                        textDecoration.add((CSSProperty.TextDecoration) t.getValue());
                }
            }
            else if (decor != TextDecoration.NONE)
                textDecoration.add(decor);
        }
        
        //color
        TermColor clr = style.getValue(TermColor.class, "color");
        if (clr != null) color = clr.getValue();
    }
    
    /** 
     * Updates a Graphics according to this context
     * @param g Graphics to be updated
     */
    public void updateGraphics(Graphics2D g)
    {
        g.setFont(font);
        g.setColor(color);
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
        g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
    }
     
    /** 
     * Updates this context according to the given style. Moreover given Graphics is updated
     * to this style and used for taking the font metrics.
     * @param style the style data to be used
     * @param g Graphics to be updated and used 
     */
    public void updateForGraphics(NodeData style, Graphics2D g)
    {
        if (style != null) update(style);
        updateGraphics(g);
        fm = g.getFontMetrics();
        
        //update the width units
        //em has been updated in update()
        
        FontRenderContext frc = new FontRenderContext(null, false, false);
        TextLayout layout = new TextLayout("x", font, frc);
        ex = layout.getBounds().getHeight();
        
        ch = fm.charWidth('0');
    }
    
    
    //-----------------------------------------------------------------------
    
    /**
     * Computes current text line height.
     * @return the height of the normal text line in pixels
     */
    public int getFontHeight()
    {
        return fm.getHeight();
    }
    
    /**
     * Obtains the maximal distance from the line top to the baseline
     * for the current font.
     * @return the baseline y offset.
     */
    public int getBaselineOffset()
    {
        return fm.getAscent();
    }
    
    /** 
     * Converts a length from a CSS length or percentage to 'pt'.
     * @param spec the CSS length specification
     * @param whole the value that corresponds to 100%. It is used only when spec is a percentage.
     * @return the length in 'pt' 
     */
    public double ptLength(TermLengthOrPercent spec, double whole)
    {
        float nval = spec.getValue();
        if (spec.isPercentage())
            return (whole * nval) / 100;
        else
        {
            TermLength.Unit unit = spec.getUnit();
            
            double ret = 0;
            if (unit == TermLength.Unit.pt)
            {
                ret = nval;
            }
            else if (unit == TermLength.Unit.in)
            {
                ret = nval * 72;
            }
            else if (unit == TermLength.Unit.cm)
            {
                ret = (nval * 72) / 2.54;
            }
            else if (unit == TermLength.Unit.mm)
            {
                ret = (nval * 72) / 25.4;
            }
            else if (unit == TermLength.Unit.pc)
            {
                ret = nval * 12;
            }
            else if (unit == TermLength.Unit.px)
            {
                ret = (nval * 72) / dpi;
            }
            else if (unit == TermLength.Unit.em)
            {
                ret = (em * nval * 72) / dpi; //em is in pixels
            }
            else if (unit == TermLength.Unit.rem)
            {
                ret = (rem * nval * 72) / dpi;
            }
            else if (unit == TermLength.Unit.ex)
            {
                ret = (ex * nval * 72) / dpi;
            }
            else if (unit == TermLength.Unit.ch)
            {
                ret = (ch * nval * 72) / dpi;
            }
            else if (unit == TermLength.Unit.vw)
            {
                return (viewport.getVisibleRect().getWidth() * nval * 72) / (100.0 * dpi);
            }
            else if (unit == TermLength.Unit.vh)
            {
                return (viewport.getVisibleRect().getHeight() * nval * 72) / (100.0 * dpi);
            }
            else if (unit == TermLength.Unit.vmin)
            {
                return (Math.min(viewport.getVisibleRect().getWidth(), viewport.getVisibleRect().getHeight()) * nval * 72) / (100.0 * dpi);
            }
            else if (unit == TermLength.Unit.vmax)
            {
                return (Math.max(viewport.getVisibleRect().getWidth(), viewport.getVisibleRect().getHeight()) * nval * 72) / (100.0 * dpi);
            }
            return ret;
        }
    }

    /** 
     * Converts a length from a CSS length to 'pt'. Percentages are always evaluated to 0.
     * @param spec the CSS length specification
     * @return font size in 'pt' 
     */
    public double ptLength(TermLengthOrPercent spec)
    {
        return ptLength(spec, 0);
    }
    
    /** 
     * Converts a length from a CSS length or percentage to 'px'.
     * @param spec the CSS length specification
     * @param whole the value that corresponds to 100%. It is used only when spec is a percentage.
     * @return the length in 'px' 
     */
    public double pxLength(TermLengthOrPercent spec, double whole)
    {
        float nval = spec.getValue();
        if (spec.isPercentage())
            return (whole * nval) / 100;
        else
        {
            TermLength.Unit unit = spec.getUnit();
            
            double ret = 0;
            if (unit == TermLength.Unit.pt)
            {
                ret = (nval * dpi) / 72;
            }
            else if (unit == TermLength.Unit.in)
            {
                ret = nval * dpi;
            }
            else if (unit == TermLength.Unit.cm)
            {
                ret = (nval * dpi) / 2.54;
            }
            else if (unit == TermLength.Unit.mm)
            {
                ret = (nval * dpi) / 25.4;
            }
            else if (unit == TermLength.Unit.pc)
            {
                ret = (nval * 12 * dpi) / 72;
            }
            else if (unit == TermLength.Unit.px)
            {
                ret = nval;
            }
            else if (unit == TermLength.Unit.em)
            {
                ret = em * nval; //em is in pixels
            }
            else if (unit == TermLength.Unit.rem)
            {
                ret = rem * nval; //em is in pixels
            }
            else if (unit == TermLength.Unit.ex)
            {
                ret = ex * nval;
            }
            else if (unit == TermLength.Unit.ch)
            {
                ret = ch * nval;
            }
            else if (unit == TermLength.Unit.vw)
            {
                return viewport.getVisibleRect().getWidth() * nval / 100.0;
            }
            else if (unit == TermLength.Unit.vh)
            {
                return viewport.getVisibleRect().getHeight() * nval / 100.0;
            }
            else if (unit == TermLength.Unit.vmin)
            {
                return Math.min(viewport.getVisibleRect().getWidth(), viewport.getVisibleRect().getHeight()) * nval / 100.0;
            }
            else if (unit == TermLength.Unit.vmax)
            {
                return Math.max(viewport.getVisibleRect().getWidth(), viewport.getVisibleRect().getHeight()) * nval / 100.0;
            }
            return ret;
        }
    }
    
    /** 
     * Converts a length from a CSS length to 'px'. Percentages are always evaluated to 0.
     * @param spec the CSS length specification
     * @return font size in 'px' 
     */
    public double pxLength(TermLengthOrPercent spec)
    {
        return pxLength(spec, 0);
    }
    
    /**
     * Converts the weight value to bold / not bold
     * @param weight a CSS weight
     * @return true if the given weight corresponds to bold
     */
    public boolean representsBold(CSSProperty.FontWeight weight)
    {
        if (weight == CSSProperty.FontWeight.BOLD ||
            weight == CSSProperty.FontWeight.BOLDER ||
            weight == CSSProperty.FontWeight.numeric_600 ||    
            weight == CSSProperty.FontWeight.numeric_700 ||    
            weight == CSSProperty.FontWeight.numeric_800 ||    
            weight == CSSProperty.FontWeight.numeric_900)
        {
            return true;
        }
        else
            return false;
    }
    
    /** 
     * Scans a list of font definitions and chooses the first one that is available
     * @param list of terms obtained from the font-family property
     * @return a font name string according to java.awt.Font
     */
    public String getFontName(TermList list)
    {
        String avail[] = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
        for (Term term : list)
        {
            Object value = term.getValue();
            if (value instanceof CSSProperty.FontFamily)
                return ((CSSProperty.FontFamily) value).getAWTValue();
            else
            {
                String name = fontAvailable(value.toString(), avail);
                if (name != null) return name;
            }
        }
        //nothing found, use Serif
        return java.awt.Font.SERIF;
    }
    
    /** Returns true if the font family is available.
     * @return The exact name of the font family or null if it's not available
     */
    private String fontAvailable(String family, String[] avail)
    {
        for (int i = 0; i < avail.length; i++)
            if (avail[i].equalsIgnoreCase(family)) return avail[i];
        return null;
    }
    
    /**
     * Creates a new java Color instance according to a CSS specification rgb(r,g,b)
     * @param spec the CSS color specification
     * @return the Color instance
     */
    public Color getColor(String spec)
    {
        if (spec.startsWith("rgb("))
        {
            String s = spec.substring(4, spec.length() - 1);
            String[] lst = s.split(",");
            try {
                int r = Integer.parseInt(lst[0].trim());
                int g = Integer.parseInt(lst[1].trim());
                int b = Integer.parseInt(lst[2].trim());
                return new Color(r, g, b);
            } catch (NumberFormatException e) {
                return null;
            }
        }
        else
            return null;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy