/******************************************************************************* * Copyright (c) 2004, 2008 John Krasnay and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * John Krasnay - initial API and implementation *******************************************************************************/ package net.sf.vex.layout; import java.util.ArrayList; import java.util.List; import net.sf.vex.ToolkitPlugin; import net.sf.vex.core.Drawable; import net.sf.vex.core.Graphics; import net.sf.vex.core.Rectangle; import net.sf.vex.css.CSS; import net.sf.vex.css.StyleSheet; import net.sf.vex.css.Styles; import net.sf.vex.dom.Element; /** * A block box corresponding to a DOM Element. Block boxes lay their * children out stacked top to bottom. Block boxes correspond to the * <code>display: block;</code> CSS property. */ public class BlockElementBox extends AbstractBlockBox { /** hspace btn. list-item bullet and block, as fraction of font-size */ static final float BULLET_SPACE = 0.5f; /** vspace btn. list-item bullet and baseine, as fraction of font-size */ //private static final float BULLET_LIFT = 0.1f; /** number of boxes created since VM startup, for profiling */ private static int boxCount; BlockBox beforeMarker; /** * Class constructor. This box's children are not created here but in the first * call to layout. Instead, we estimate the box's height here based on the given width. * @param context LayoutContext used for this layout. * @param parent This box's parent box. * @param element Element to which this box corresponds. */ public BlockElementBox(LayoutContext context, BlockBox parent, Element element) { super(context, parent, element); } /** * Returns the number of boxes created since VM startup. Used for profiling. */ public static int getBoxCount() { return boxCount; } public int getEndOffset() { return this.getElement().getEndOffset(); } public int getStartOffset() { return this.getElement().getStartOffset() + 1; } public boolean hasContent() { return true; } public void paint(LayoutContext context, int x, int y) { super.paint(context, x, y); if (this.beforeMarker != null) { this.beforeMarker.paint( context, x + this.beforeMarker.getX(), y + this.beforeMarker.getY()); } } protected int positionChildren(LayoutContext context) { int repaintStart = super.positionChildren(context); Styles styles = context.getStyleSheet().getStyles(this.getElement()); if (this.beforeMarker != null) { int x = - this.beforeMarker.getWidth() - Math.round(BULLET_SPACE * styles.getFontSize()); int y = this.getFirstLineTop(context); LineBox firstLine = this.getFirstLine(); if (firstLine != null) { y += firstLine.getBaseline() - this.beforeMarker.getFirstLine().getBaseline(); } this.beforeMarker.setX(x); this.beforeMarker.setY(y); } return repaintStart; } public String toString() { return "BlockElementBox: <" + this.getElement().getName() + ">" + "[x=" + this.getX() + ",y=" + this.getY() + ",width=" + this.getWidth() + ",height=" + this.getHeight() + "]"; } //===================================================== PRIVATE /** * Lays out the children as vertically stacked blocks. Runs of * text and inline elements are wrapped in DummyBlockBox's. */ protected List createChildren(LayoutContext context) { long start = 0; if (ToolkitPlugin.getInstance().isDebugging()) { start = System.currentTimeMillis(); } Element element = this.getElement(); int width = this.getWidth(); List childList = new ArrayList(); StyleSheet ss = context.getStyleSheet(); // element and styles for generated boxes Element genElement; Styles genStyles; // :before content List beforeInlines = null; genElement = context.getStyleSheet().getBeforeElement(this.getElement()); if (genElement != null) { genStyles = ss.getStyles(genElement); if (genStyles.getDisplay().equals(CSS.INLINE)) { beforeInlines = new ArrayList(); beforeInlines.addAll(LayoutUtils.createGeneratedInlines(context, genElement)); } else { childList.add(new BlockPseudoElementBox(context, genElement, this, width)); } } // :after content Box afterBlock = null; List afterInlines = null; genElement = context.getStyleSheet().getAfterElement(this.getElement()); if (genElement != null) { genStyles = context.getStyleSheet().getStyles(genElement); if (genStyles.getDisplay().equals(CSS.INLINE)) { afterInlines = new ArrayList(); afterInlines.addAll(LayoutUtils.createGeneratedInlines(context, genElement)); } else { afterBlock = new BlockPseudoElementBox(context, genElement, this, width); } } int startOffset = element.getStartOffset() + 1; int endOffset = element.getEndOffset(); childList.addAll(createBlockBoxes(context, startOffset, endOffset, width, beforeInlines, afterInlines)); if (afterBlock != null) { childList.add(afterBlock); } Styles styles = context.getStyleSheet().getStyles(this.getElement()); if (styles.getDisplay().equals(CSS.LIST_ITEM) && !styles.getListStyleType().equals(CSS.NONE)) { this.createListMarker(context); } if (ToolkitPlugin.getInstance().isDebugging()) { long end = System.currentTimeMillis(); if (end - start > 10) { System.out.println("BEB.layout for " + this.getElement().getName() + " took " + (end-start) + "ms"); } } return childList; } /** * Creates a marker box for this primary box and puts it in the * beforeMarker field. */ private void createListMarker(LayoutContext context) { Styles styles = context.getStyleSheet().getStyles(this.getElement()); InlineBox markerInline; String type =styles.getListStyleType(); if (type.equals(CSS.NONE)) { return; } else if (type.equals(CSS.CIRCLE)) { markerInline = createCircleBullet(this.getElement(), styles); } else if (type.equals(CSS.SQUARE)) { markerInline = createSquareBullet(this.getElement(), styles); } else if (isEnumeratedListStyleType(type)) { String item = this.getItemNumberString(type); markerInline = new StaticTextBox(context, this.getElement(), item + "."); } else { markerInline = createDiscBullet(this.getElement(), styles); } this.beforeMarker = ParagraphBox.create( context, this.getElement(), new InlineBox[] { markerInline }, Integer.MAX_VALUE); } /** * Returns a Drawable that draws a circle-style list item bullet. */ private static InlineBox createCircleBullet(Element element, Styles styles) { final int size = Math.round(0.5f * styles.getFontSize()); final int lift = Math.round(0.1f * styles.getFontSize()); Drawable drawable = new Drawable() { public void draw(Graphics g, int x, int y) { g.setLineStyle(Graphics.LINE_SOLID); g.setLineWidth(1); g.drawOval(x, y - size - lift, size, size); } public Rectangle getBounds() { return new Rectangle(0, -size - lift, size, size); } }; return new DrawableBox(drawable, element); } /** * Returns a Drawable that draws a disc-style list item bullet. */ private static InlineBox createDiscBullet(Element element, Styles styles) { final int size = Math.round(0.5f * styles.getFontSize()); final int lift = Math.round(0.1f * styles.getFontSize()); Drawable drawable = new Drawable() { public void draw(Graphics g, int x, int y) { g.fillOval(x, y - size - lift, size, size); } public Rectangle getBounds() { return new Rectangle(0, -size - lift, size, size); } }; return new DrawableBox(drawable, element); } /** * Returns a Drawable that draws a square-style list item bullet. */ private static InlineBox createSquareBullet(Element element, Styles styles) { final int size = Math.round(0.5f * styles.getFontSize()); final int lift = Math.round(0.1f * styles.getFontSize()); Drawable drawable = new Drawable() { public void draw(Graphics g, int x, int y) { g.setLineStyle(Graphics.LINE_SOLID); g.setLineWidth(1); g.drawRect(x, y - size - lift, size, size); } public Rectangle getBounds() { return new Rectangle(0, -size - lift, size, size); } }; return new DrawableBox(drawable, element); } /** * Returns the vertical distance from the top of this box to the top * of its first line. */ int getFirstLineTop(LayoutContext context) { Styles styles = context.getStyleSheet().getStyles(this.getElement()); int top = styles.getBorderTopWidth() + styles.getPaddingTop().get(0); Box[] children = this.getChildren(); if (children != null && children.length > 0 && children[0] instanceof BlockElementBox) { return top + ((BlockElementBox) children[0]).getFirstLineTop(context); } else { return top; } } /** * Returns the item number of this box. The item number indicates the * ordinal number of the corresponding element amongst its siblings * starting with 1. */ private int getItemNumber() { Element element = this.getElement(); Element parent = element.getParent(); if (parent == null) { return 1; } int item = 1; Element[] children = parent.getChildElements(); for (int i = 0; i < children.length; i++) { if (children[i] == element) { return item; } if (children[i].getName().equals(element.getName())) { item++; } } throw new IllegalStateException(); } private String getItemNumberString(String style) { int item = getItemNumber(); if (style.equals(CSS.DECIMAL_LEADING_ZERO)) { if (item < 10) { return "0" + Integer.toString(item); } else { return Integer.toString(item); } } else if (style.equals(CSS.LOWER_ALPHA) || style.equals(CSS.LOWER_LATIN)) { return this.getAlpha(item); } else if (style.equals(CSS.LOWER_ROMAN)) { return this.getRoman(item); } else if (style.equals(CSS.UPPER_ALPHA) || style.equals(CSS.UPPER_LATIN)) { return this.getAlpha(item).toUpperCase(); } else if (style.equals(CSS.UPPER_ROMAN)) { return this.getRoman(item).toUpperCase(); } else { return Integer.toString(item); } } private String getAlpha(int n) { final String alpha = "abcdefghijklmnopqrstuvwxyz"; return String.valueOf(alpha.charAt((n-1) % 26)); } private String getRoman(int n) { final String[] ones = { "", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix" }; final String[] tens = { "", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc" }; final String[] hundreds = { "", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm" }; StringBuffer sb = new StringBuffer(); for (int i = 0; i < n/1000; i++) { sb.append("m"); } sb.append(hundreds[(n/100) % 10]); sb.append(tens[(n/10) % 10]); sb.append(ones[n % 10]); return sb.toString(); } private static boolean isEnumeratedListStyleType(String s) { return s.equals(CSS.ARMENIAN) || s.equals(CSS.CJK_IDEOGRAPHIC) || s.equals(CSS.DECIMAL) || s.equals(CSS.DECIMAL_LEADING_ZERO) || s.equals(CSS.GEORGIAN) || s.equals(CSS.HEBREW) || s.equals(CSS.HIRAGANA) || s.equals(CSS.HIRAGANA_IROHA) || s.equals(CSS.KATAKANA) || s.equals(CSS.KATAKANA_IROHA) || s.equals(CSS.LOWER_ALPHA) || s.equals(CSS.LOWER_GREEK) || s.equals(CSS.LOWER_LATIN) || s.equals(CSS.LOWER_ROMAN) || s.equals(CSS.UPPER_ALPHA) || s.equals(CSS.UPPER_LATIN) || s.equals(CSS.UPPER_ROMAN); } }