/* * {{{ header & license * Copyright (c) 2006 Wisconsin Court System * * This program 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 2.1 * of the License, or (at your option) any later version. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * }}} */ package org.xhtmlrenderer.context; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.w3c.dom.css.CSSPrimitiveValue; import org.xhtmlrenderer.css.constants.IdentValue; import org.xhtmlrenderer.css.extend.ContentFunction; import org.xhtmlrenderer.css.parser.FSFunction; import org.xhtmlrenderer.css.parser.PropertyValue; import org.xhtmlrenderer.layout.CounterFunction; import org.xhtmlrenderer.layout.InlineBoxing; import org.xhtmlrenderer.layout.LayoutContext; import org.xhtmlrenderer.render.Box; import org.xhtmlrenderer.render.InlineText; import org.xhtmlrenderer.render.InlineLayoutBox; import org.xhtmlrenderer.render.LineBox; import org.xhtmlrenderer.render.PageBox; import org.xhtmlrenderer.render.RenderingContext; public class ContentFunctionFactory { private List _functions = new ArrayList(); { _functions.add(new PageCounterFunction()); _functions.add(new PagesCounterFunction()); _functions.add(new TargetCounterFunction()); _functions.add(new LeaderFunction()); } public ContentFunction lookupFunction(LayoutContext c, FSFunction function) { for (Iterator i = _functions.iterator(); i.hasNext(); ) { ContentFunction f = (ContentFunction)i.next(); if (f.canHandle(c, function)) { return f; } } return null; } public void registerFunction(ContentFunction function) { _functions.add(function); } private static abstract class PageNumberFunction implements ContentFunction { public boolean isStatic() { return false; } public String calculate(LayoutContext c, FSFunction function) { return null; } public String getLayoutReplacementText() { return "999"; } protected IdentValue getListStyleType(FSFunction function) { IdentValue result = IdentValue.DECIMAL; List parameters = function.getParameters(); if (parameters.size() == 2) { PropertyValue pValue = (PropertyValue)parameters.get(1); IdentValue iValue = IdentValue.valueOf(pValue.getStringValue()); if (iValue != null) { result = iValue; } } return result; } protected boolean isCounter(FSFunction function, String counterName) { if (function.getName().equals("counter")) { List parameters = function.getParameters(); if (parameters.size() == 1 || parameters.size() == 2) { PropertyValue param = (PropertyValue)parameters.get(0); if (param.getPrimitiveType() != CSSPrimitiveValue.CSS_IDENT || ! param.getStringValue().equals(counterName)) { return false; } if (parameters.size() == 2) { param = (PropertyValue)parameters.get(1); if (param.getPrimitiveType() != CSSPrimitiveValue.CSS_IDENT) { return false; } } return true; } } return false; } } private static class PageCounterFunction extends PageNumberFunction implements ContentFunction { public String calculate(RenderingContext c, FSFunction function, InlineText text) { int value = c.getRootLayer().getRelativePageNo(c) + 1; return CounterFunction.createCounterText(getListStyleType(function), value); } public boolean canHandle(LayoutContext c, FSFunction function) { return c.isPrint() && isCounter(function, "page"); } } private static class PagesCounterFunction extends PageNumberFunction implements ContentFunction { public String calculate(RenderingContext c, FSFunction function, InlineText text) { int value = c.getRootLayer().getRelativePageCount(c); return CounterFunction.createCounterText(getListStyleType(function), value); } public boolean canHandle(LayoutContext c, FSFunction function) { return c.isPrint() && isCounter(function, "pages"); } } /** * Partially implements target counter as specified here: * http://www.w3.org/TR/2007/WD-css3-gcpm-20070504/#cross-references */ private static class TargetCounterFunction implements ContentFunction { public boolean isStatic() { return false; } public String calculate(RenderingContext c, FSFunction function, InlineText text) { String uri = text.getParent().getElement().getAttribute("href"); if (uri != null && uri.startsWith("#")) { String anchor = uri.substring(1); Box target = c.getBoxById(anchor); if (target != null) { PageBox targetPage = c.getRootLayer().getPage(c, target.getAbsY()); return CounterFunction.createCounterText(IdentValue.DECIMAL, targetPage.getPageNo()+1); } } return ""; } public String calculate(LayoutContext c, FSFunction function) { return null; } public String getLayoutReplacementText() { return "999"; } public boolean canHandle(LayoutContext c, FSFunction function) { if (c.isPrint() && function.getName().equals("target-counter")) { List parameters = function.getParameters(); if (parameters.size() == 2 || parameters.size() == 3) { FSFunction f = ((PropertyValue)parameters.get(0)).getFunction(); if (f == null || f.getParameters().size() != 1 || ((PropertyValue)f.getParameters().get(0)).getPrimitiveType() != CSSPrimitiveValue.CSS_IDENT || ! ((PropertyValue)f.getParameters().get(0)).getStringValue().equals("href")) { return false; } PropertyValue param = (PropertyValue)parameters.get(1); if (param.getPrimitiveType() != CSSPrimitiveValue.CSS_IDENT || ! param.getStringValue().equals("page")) { return false; } return true; } } return false; } } /** * Partially implements leaders as specified here: * http://www.w3.org/TR/2007/WD-css3-gcpm-20070504/#leaders */ private static class LeaderFunction implements ContentFunction { public boolean isStatic() { return false; } public String calculate(RenderingContext c, FSFunction function, InlineText text) { InlineLayoutBox iB = text.getParent(); LineBox lineBox = iB.getLineBox(); // There might be a target-counter function after this function. // Because the leader should fill up the line, we need the correct // width and must first compute the target-counter function. boolean dynamic = false; Iterator childIterator = lineBox.getChildIterator(); while (childIterator.hasNext()) { Box child = (Box)childIterator.next(); if (child == iB) { dynamic = true; } else if (dynamic && child instanceof InlineLayoutBox) { ((InlineLayoutBox)child).lookForDynamicFunctions(c); } } if (dynamic) { int totalLineWidth = InlineBoxing.positionHorizontally(c, lineBox, 0); lineBox.setContentWidth(totalLineWidth); } // Get leader value and value width PropertyValue param = (PropertyValue)function.getParameters().get(0); String value = param.getStringValue(); if (param.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) { if (value.equals("dotted")) { value = ". "; } else if (value.equals("solid")) { value = "_"; } else if (value.equals("space")) { value = " "; } } // Compute value width using 100x string to get more precise width. // Otherwise there might be a small gap at the right side. This is // necessary because a TextRenderer usually use double/float for width. StringBuffer tmp = new StringBuffer(100 * value.length()); for (int i = 0; i < 100; i++) { tmp.append(value); } float valueWidth = c.getTextRenderer().getWidth(c.getFontContext(), iB.getStyle().getFSFont(c), tmp.toString()) / 100f; int spaceWidth = c.getTextRenderer().getWidth(c.getFontContext(), iB.getStyle().getFSFont(c), " "); // compute leader width and necessary count of values int leaderWidth = iB.getContainingBlockWidth() - iB.getLineBox().getWidth() + text.getWidth(); int count = (int) ((leaderWidth - (2 * spaceWidth)) / valueWidth); // build leader string StringBuffer buf = new StringBuffer(count * value.length() + 2); buf.append(' '); for (int i = 0; i < count; i++) { buf.append(value); } buf.append(' '); String leaderString = buf.toString(); // set left margin to ensure that the leader is right aligned (for TOC) int leaderStringWidth = c.getTextRenderer().getWidth(c.getFontContext(), iB.getStyle().getFSFont(c), leaderString); iB.setMarginLeft(c, leaderWidth - leaderStringWidth); return leaderString; } public String calculate(LayoutContext c, FSFunction function) { return null; } public String getLayoutReplacementText() { return " . "; } public boolean canHandle(LayoutContext c, FSFunction function) { if (c.isPrint() && function.getName().equals("leader")) { List parameters = function.getParameters(); if (parameters.size() == 1) { PropertyValue param = (PropertyValue)parameters.get(0); if (param.getPrimitiveType() != CSSPrimitiveValue.CSS_STRING && (param.getPrimitiveType() != CSSPrimitiveValue.CSS_IDENT || (!param.getStringValue().equals("dotted") && !param.getStringValue().equals("solid") && !param.getStringValue().equals("space")))) { return false; } return true; } } return false; } } }