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

org.fit.cssbox.layout.Viewport 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
/*
 * Viewport.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 .
 */

package org.fit.cssbox.layout;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.Vector;

import org.fit.cssbox.render.BoxRenderer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;

import cz.vutbr.web.css.CSSFactory;

/**
 * The viewport is a special case of BlockElement that has several widths and heights:
 * 
 * 
    *
  • Viewport size - the width and height of the visible area used for * computing the sizes of the contained blocks.
  • *
  • Canvas size - the width and height of the whole rendered page
  • * * @author radek */ public class Viewport extends BlockBox { private static Logger log = LoggerFactory.getLogger(Viewport.class); /** Total canvas width */ private int width; /** Total canvas height */ private int height; /** Visible rectagle -- the position and size of the CSS viewport */ private Rectangle visibleRect; protected BrowserConfig config; private BoxFactory factory; private BoxRenderer renderer; private Element root; //the DOM root private ElementBox rootBox; //the box that corresponds to the root node. It should be one of the child boxes. private ElementBox bgSrc; //source element for the background private boolean rootOverflowVisible = true; //has the root box originally had overflow:visible? private int maxx; //maximal X position of all the content private int maxy; //maximal Y position of all the content private boolean recomputeAbs; //indicates that the absolute positions need to be recomputed /** * Creates a new Viewport with the given initial size. The actual size may be increased during the layout. * * @param e The anonymous element representing the viewport. * @param g * @param ctx * @param factory The factory used for creating the child boxes. * @param root The root element of the rendered document. * @param width Preferred (minimal) width. * @param height Preferred (minimal) height. */ public Viewport(Element e, Graphics2D g, VisualContext ctx, BoxFactory factory, Element root, int width, int height) { super(e, g, ctx); ctx.setViewport(this); this.factory = factory; this.root = root; style = CSSFactory.createNodeData(); //Viewport starts with an empty style nested = new Vector(); startChild = 0; endChild = 0; this.width = width; this.height = height; isblock = true; contblock = true; root = null; visibleRect = new Rectangle(0, 0, width, height); } /** * Obtains the position of the visible part (CSS viewport) in the canvas. * @return the visible rectangle */ public Rectangle getVisibleRect() { return visibleRect; } /** * Sets the position of the visible part (CSS viewport) in the canvas. * @param visibleRect the visible rectangle to be set */ public void setVisibleRect(Rectangle visibleRect) { this.visibleRect = visibleRect; this.content = visibleRect.getSize(); } /** * Obtains the size of the whole canvas that represents the whole rendered page. * @return The canvas size. */ public Dimension getCanvasSize() { return new Dimension(width, height); } /** * Obtains the current browser configuration. * @return current configuration. */ public BrowserConfig getConfig() { return config; } /** * Sets the browser configuration used for rendering. * @param config the new configuration. */ public void setConfig(BrowserConfig config) { this.config = config; overflowX = overflowY = config.getClipViewport() ? OVERFLOW_HIDDEN : OVERFLOW_VISIBLE; } @Override public void initSubtree() { super.initSubtree(); loadBackgroundFromContents(); } @Override public String toString() { return "Viewport " + width + "x" + height + "[visible " + visibleRect.x + "," +visibleRect.y + "," + visibleRect.width + "," + visibleRect.height + "]"; } public BoxFactory getFactory() { return factory; } public int getMinimalWidthLimit() { return width; } /** * Obtains the DOM root element. * @return The root element of the document. */ public Element getRootElement() { return root; } /** * Obtains the child box the corresponds to the DOM root element. * @return the corresponding element box or null if the viewport is empty. */ public ElementBox getRootBox() { return rootBox; } public ElementBox getElementBoxByName(String name, boolean case_sensitive) { return recursiveFindElementBoxByName(this, name, case_sensitive); } @Override public void addSubBox(Box box) { super.addSubBox(box); if (box instanceof ElementBox && ((ElementBox) box).getElement() == root) { if (rootBox != null) log.debug("Viewport warning: another root box '" + box + "' in addition to previous '" + rootBox + "'"); box.makeRoot(); rootBox = (ElementBox) box; } } @Override public boolean hasFixedHeight() { return true; } @Override public boolean hasFixedWidth() { return true; } @Override public boolean canIncreaseWidth() { return true; } @Override public boolean isVisible() { return true; } @Override public boolean visibleInClip(Box box) { if (config.getClipViewport()) return super.visibleInClip(box); else { //not clipping - everything that is in positive coordinates is visible in viewport Rectangle bb; if (box instanceof ElementBox) bb = ((ElementBox) box).getAbsoluteBorderBounds(); else bb = box.getAbsoluteBounds(); return (bb.x + bb.width > 0) && (bb.y + bb.height > 0); } } @Override protected boolean separatedFromTop(ElementBox box) { return true; } @Override protected boolean separatedFromBottom(ElementBox box) { return true; } @Override public void setSize(int width, int height) { this.width = width; this.height = height; content = new Dimension(width, height); bounds = new Rectangle(0, 0, totalWidth(), totalHeight()); } @Override public Viewport getViewport() { return this; } @Override protected void loadPosition() { position = BlockBox.POS_ABSOLUTE; topset = true; leftset = true; bottomset = false; rightset = false; coords = new LengthSet(0, 0, 0, 0); } @Override protected void loadSizes(boolean update) { if (!update) { margin = new LengthSet(); emargin = new LengthSet(); declMargin = new LengthSet(); border = new LengthSet(); padding = new LengthSet(); content = new Dimension(width, height); min_size = new Dimension(-1, -1); max_size = new Dimension(-1, -1); loadPosition(); } bounds = new Rectangle(0, 0, totalWidth(), totalHeight()); } @Override public void setContentWidth(int width) { //do not descrease the viewport width under the initial value if (width > this.width) super.setContentWidth(width); } @Override public void setContentHeight(int height) { //do not descrease the viewport height under the initial value if (height > this.height) super.setContentHeight(height); } @Override protected void loadBackground() { bgcolor = null; //during the initialization, the background is not known } /** * Gets the box that was used as the source of the viewport background (usually the root box or the body box). * @return The corresponding element box or {@code null} when no such box was found (e.g. an empty rendering tree). */ public ElementBox getBackgroundSource() { return bgSrc; } /** * Use the root box or the body box (for HTML documents) for obtaining the backgrounds. */ private void loadBackgroundFromContents() { if (rootBox != null && bgSrc == null) { ElementBox src = rootBox; if (src.getBgcolor() == null && src.getBackgroundImages() == null && config.getUseHTML()) //for HTML, try to use the body src = getElementBoxByName("body", false); if (src != null) { bgSrc = src; if (src.getBgcolor() != null) { bgcolor = src.getBgcolor(); src.setBgcolor(null); } else if (bgcolor == null) //use white color when not set already and the body is transparent bgcolor = config.getViewportBackgroundColor(); if (src.getBackgroundImages() != null && !src.getBackgroundImages().isEmpty()) { bgimages = loadBackgroundImages(src.getStyle()); src.getBackgroundImages().clear(); } } else log.debug("Couldn't find background source for viewport"); } } /** * Calculates the absolute positions and updates the viewport size * in order to enclose all the boxes. * @param min the minimal viewport dimensions */ public void updateBounds(Dimension min) { //first round - compute the viewport size maxx = min.width; maxy = min.height; absolutePositionsChildren(); //update the size if (width < maxx) width = maxx; if (height < maxy) height = maxy; loadSizes(); loadBackgroundFromContents(); //the background image positions may have changed } @Override public boolean doLayout(int availw, boolean force, boolean linestart) { //remove previously splitted children from possible previous layout clearSplitted(); //viewport has a siplified width computation algorithm int min = getMinimalContentWidth(); int pref = Math.max(min, width); setContentWidth(pref); updateChildSizes(); //the width should be fixed from this point widthComputed = true; /* Always try to use the full width. If the box is not in flow, its width * is updated after the layout */ setAvailableWidth(totalWidth()); if (!contblock) //block elements containing inline elements only layoutInline(); else //block elements containing block elements layoutBlocks(); //allways fits as well possible return true; } @Override public boolean formsStackingContext() { return true; } @Override public void absolutePositions() { if (parent == null) //viewport may be the root absbounds = new Rectangle(bounds); else //or a nested viewport absbounds = new Rectangle(parent.getAbsoluteContentBounds()); //clear this context if it exists (remove old children) if (scontext != null) scontext.clear(); absolutePositionsChildren(); } /** * Computes the absolute positions of the child boxes. */ protected void absolutePositionsChildren() { //first round: position most boxes recomputeAbs = false; for (int i = 0; i < getSubBoxNumber(); i++) getSubBox(i).absolutePositions(); if (recomputeAbs) { //second round: some reference boxes used, recompute once again if (scontext != null) //clear the stacking context if it exists -- the child contexts will register again scontext.clear(); for (int i = 0; i < getSubBoxNumber(); i++) getSubBox(i).absolutePositions(); recomputeAbs = false; } } /** * Sets the current renderer and draws the whole subtree using the given renderer. * @param renderer The renderer to be used for drawing. */ public void draw(BoxRenderer renderer) { this.renderer = renderer; drawStackingContext(false); } /** * Obtains the current renderer used for painting the boxes. * @return current renderer. */ public BoxRenderer getRenderer() { return renderer; } /** * Updates the maximal viewport size according to the element bounds */ public void updateBoundsFor(Rectangle bounds) { int x = bounds.x + bounds.width - 1; if (maxx < x) maxx = x; int y = bounds.y + bounds.height - 1; if (maxy < y) maxy = y; } /** * Indicates that the absolute positions need to be recomputed onec again. This happens when * some absolutely positioned box has a 'static' position depending on some in-flow box * and the position of the in-flow box changed. */ public void requireRecomputePositions() { recomputeAbs = true; } /** * Uses the given block as a clipping block instead of the default Viewport. * @param block the new clipping block */ public void clipByBlock(BlockBox block) { recursivelySetClipBlock(this, block); } private void recursivelySetClipBlock(Box root, BlockBox clip) { if (root == this || root.getClipBlock() == this) { root.setClipBlock(clip); if (root instanceof ElementBox) { ElementBox eb = (ElementBox) root; for (int i = eb.getStartChild(); i < eb.getEndChild(); i++) recursivelySetClipBlock(eb.getSubBox(i), clip); } } } /** * Tries to propagate the overflow value to viewport from the element based on * https://drafts.csswg.org/css-overflow-3/#overflow-propagation * If the conditions are met, the overflow values are propagated. Othervise, no action is taken. * * @param block the box to be used as a possible source of the overflow values * @return {@code true} when the propagation is finished (no more boxes may be used as the source), {@code false} when other boxes should be tried. */ public boolean checkPropagateOverflow(BlockBox block) { if (rootBox == null && block.getElement() == root) //this block will become a new root block; use it { rootOverflowVisible = (block.getOverflowX() == BlockBox.OVERFLOW_VISIBLE); takeBoxOverflow(block); return !rootOverflowVisible || !config.getUseHTML(); //in HTML mode, the "body" element may be used as well thus we should continue when root element is not visible } else if ("body".equals(block.getElement().getTagName())) { if (config.getUseHTML()) takeBoxOverflow(block); return true; //that should be all, we are finished } else return false; //this one could not be used, go on } private void takeBoxOverflow(BlockBox srcBlock) { //use the propagated value for the viewport overflowX = srcBlock.overflowX; overflowY = srcBlock.overflowY; //use VISIBLE for the original block srcBlock.overflowX = BlockBox.OVERFLOW_VISIBLE; srcBlock.overflowY = BlockBox.OVERFLOW_VISIBLE; //apply CSSBox-specific viewport clipping config if (config.getClipViewport()) { overflowX = BlockBox.OVERFLOW_HIDDEN; overflowY = BlockBox.OVERFLOW_HIDDEN; } else { //CSSBox treats the overflow:visible as scrollable for now, i.e. scroll and auto values should be converted to visible if (overflowX != BlockBox.OVERFLOW_HIDDEN) overflowX = BlockBox.OVERFLOW_VISIBLE; if (overflowY != BlockBox.OVERFLOW_HIDDEN) overflowY = BlockBox.OVERFLOW_VISIBLE; } } //=================================================================================== @Override public int getContentX() { return visibleRect.x; } @Override public int getContentY() { return visibleRect.y; } @Override public int getContentWidth() { return visibleRect.width; } @Override public int getContentHeight() { return visibleRect.height; } @Override public Rectangle getAbsoluteBackgroundBounds() { return new Rectangle(visibleRect); } @Override public Rectangle getAbsoluteBorderBounds() { return new Rectangle(visibleRect); } @Override public Rectangle getClippedBounds() { if (config.getClipViewport()) return getAbsoluteBounds(); else return new Rectangle(0, 0, width, height); } @Override public Rectangle getClippedContentBounds() { if (config.getClipViewport()) return getAbsoluteBounds(); else return new Rectangle(0, 0, width, height); } @Override public void drawBackground(Graphics2D g) { //fill the complete packground with the background color first if (bgcolor != null) { Color color = g.getColor(); g.setColor(bgcolor); g.fillRect(0, 0, width, height); g.setColor(color); } super.drawBackground(g); } //=================================================================================== private ElementBox recursiveFindElementBoxByName(ElementBox ebox, String name, boolean case_sensitive) { boolean eq; if (case_sensitive) eq = ebox.getElement().getTagName().equals(name); else eq = ebox.getElement().getTagName().equalsIgnoreCase(name); if (eq) return ebox; else { ElementBox ret = null; for (int i = 0; i < ebox.getSubBoxNumber() && ret == null; i++) { Box child = ebox.getSubBox(i); if (child instanceof ElementBox) ret = recursiveFindElementBoxByName((ElementBox) child, name, case_sensitive); } return ret; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy