/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* $Id$ */ package org.apache.fop.render.ps; import java.awt.Color; import java.awt.Point; import java.io.IOException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.xmlgraphics.ps.PSGenerator; import org.apache.fop.fo.Constants; import org.apache.fop.render.intermediate.ArcToBezierCurveTransformer; import org.apache.fop.render.intermediate.BezierCurvePainter; import org.apache.fop.render.intermediate.BorderPainter; import org.apache.fop.render.intermediate.GraphicsPainter; import org.apache.fop.traits.RuleStyle; import org.apache.fop.util.ColorUtil; /** * PostScript-specific implementation of the {@link BorderPainter}. */ public class PSGraphicsPainter implements GraphicsPainter, BezierCurvePainter { /** logging instance */ private static Log log = LogFactory.getLog(PSGraphicsPainter.class); private final PSGenerator generator; /** Used for drawing arcs since PS does not natively support drawing elliptic curves */ private final ArcToBezierCurveTransformer arcToBezierCurveTransformer; /** * Creates a new border painter for PostScript. * @param generator the PostScript generator */ public PSGraphicsPainter(PSGenerator generator) { this.generator = generator; this.arcToBezierCurveTransformer = new ArcToBezierCurveTransformer(this); } /** {@inheritDoc} */ public void drawBorderLine(int x1, int y1, int x2, int y2, boolean horz, boolean startOrBefore, int style, Color col) throws IOException { drawBorderLine(generator, toPoints(x1), toPoints(y1), toPoints(x2), toPoints(y2), horz, startOrBefore, style, col); } private static void drawLine(PSGenerator gen, float startx, float starty, float endx, float endy) throws IOException { gen.writeln(gen.formatDouble(startx) + " " + gen.formatDouble(starty) + " " + gen.mapCommand("moveto") + " " + gen.formatDouble(endx) + " " + gen.formatDouble(endy) + " " + gen.mapCommand("lineto") + " " + gen.mapCommand("stroke") + " " + gen.mapCommand("newpath")); } /** {@inheritDoc} */ public static void drawBorderLine(PSGenerator gen, float x1, float y1, float x2, float y2, boolean horz, boolean startOrBefore, int style, Color col) throws IOException { float w = x2 - x1; float h = y2 - y1; if ((w < 0) || (h < 0)) { log.error("Negative extent received. Border won't be painted."); return; } switch (style) { case Constants.EN_DASHED: gen.useColor(col); if (horz) { float dashWidth = BorderPainter.dashWidthCalculator(w, h); gen.useDash("[" + dashWidth + " " + BorderPainter.DASHED_BORDER_SPACE_RATIO * dashWidth + "] 0"); gen.useLineCap(0); gen.useLineWidth(h); float ym = y1 + (h / 2); drawLine(gen, x1, ym, x2, ym); } else { float dashWidth = BorderPainter.dashWidthCalculator(h, w); gen.useDash("[" + dashWidth + " " + BorderPainter.DASHED_BORDER_SPACE_RATIO * dashWidth + "] 0"); gen.useLineCap(0); gen.useLineWidth(w); float xm = x1 + (w / 2); drawLine(gen, xm, y1, xm, y2); } break; case Constants.EN_DOTTED: gen.useColor(col); gen.useLineCap(1); //Rounded! if (horz) { float unit = Math.abs(2 * h); int rep = (int) (w / unit); if (rep % 2 == 0) { rep++; } unit = w / rep; gen.useDash("[0 " + unit + "] 0"); gen.useLineWidth(h); float ym = y1 + (h / 2); drawLine(gen, x1, ym, x2, ym); } else { float unit = Math.abs(2 * w); int rep = (int) (h / unit); if (rep % 2 == 0) { rep++; } unit = h / rep; gen.useDash("[0 " + unit + "] 0"); gen.useLineWidth(w); float xm = x1 + (w / 2); drawLine(gen, xm, y1, xm, y2); } break; case Constants.EN_DOUBLE: gen.useColor(col); gen.useDash(null); if (horz) { float h3 = h / 3; gen.useLineWidth(h3); float ym1 = y1 + (h3 / 2); float ym2 = ym1 + h3 + h3; drawLine(gen, x1, ym1, x2, ym1); drawLine(gen, x1, ym2, x2, ym2); } else { float w3 = w / 3; gen.useLineWidth(w3); float xm1 = x1 + (w3 / 2); float xm2 = xm1 + w3 + w3; drawLine(gen, xm1, y1, xm1, y2); drawLine(gen, xm2, y1, xm2, y2); } break; case Constants.EN_GROOVE: case Constants.EN_RIDGE: float colFactor = (style == Constants.EN_GROOVE ? 0.4f : -0.4f); gen.useDash(null); if (horz) { Color uppercol = ColorUtil.lightenColor(col, -colFactor); Color lowercol = ColorUtil.lightenColor(col, colFactor); float h3 = h / 3; gen.useLineWidth(h3); float ym1 = y1 + (h3 / 2); gen.useColor(uppercol); drawLine(gen, x1, ym1, x2, ym1); gen.useColor(col); drawLine(gen, x1, ym1 + h3, x2, ym1 + h3); gen.useColor(lowercol); drawLine(gen, x1, ym1 + h3 + h3, x2, ym1 + h3 + h3); } else { Color leftcol = ColorUtil.lightenColor(col, -colFactor); Color rightcol = ColorUtil.lightenColor(col, colFactor); float w3 = w / 3; gen.useLineWidth(w3); float xm1 = x1 + (w3 / 2); gen.useColor(leftcol); drawLine(gen, xm1, y1, xm1, y2); gen.useColor(col); drawLine(gen, xm1 + w3, y1, xm1 + w3, y2); gen.useColor(rightcol); drawLine(gen, xm1 + w3 + w3, y1, xm1 + w3 + w3, y2); } break; case Constants.EN_INSET: case Constants.EN_OUTSET: colFactor = (style == Constants.EN_OUTSET ? 0.4f : -0.4f); gen.useDash(null); if (horz) { Color c = ColorUtil.lightenColor(col, (startOrBefore ? 1 : -1) * colFactor); gen.useLineWidth(h); float ym1 = y1 + (h / 2); gen.useColor(c); drawLine(gen, x1, ym1, x2, ym1); } else { Color c = ColorUtil.lightenColor(col, (startOrBefore ? 1 : -1) * colFactor); gen.useLineWidth(w); float xm1 = x1 + (w / 2); gen.useColor(c); drawLine(gen, xm1, y1, xm1, y2); } break; case Constants.EN_HIDDEN: break; default: gen.useColor(col); gen.useDash(null); gen.useLineCap(0); if (horz) { gen.useLineWidth(h); float ym = y1 + (h / 2); drawLine(gen, x1, ym, x2, ym); } else { gen.useLineWidth(w); float xm = x1 + (w / 2); drawLine(gen, xm, y1, xm, y2); } } } /** {@inheritDoc} */ public void drawLine(Point start, Point end, int width, Color color, RuleStyle style) throws IOException { if (start.y != end.y) { //TODO Support arbitrary lines if necessary throw new UnsupportedOperationException( "Can only deal with horizontal lines right now"); } saveGraphicsState(); int half = width / 2; int starty = start.y - half; //Rectangle boundingRect = new Rectangle(start.x, start.y - half, end.x - start.x, width); switch (style.getEnumValue()) { case Constants.EN_SOLID: case Constants.EN_DASHED: case Constants.EN_DOUBLE: drawBorderLine(start.x, starty, end.x, starty + width, true, true, style.getEnumValue(), color); break; case Constants.EN_DOTTED: clipRect(start.x, starty, end.x - start.x, width); //This displaces the dots to the right by half a dot's width //TODO There's room for improvement here generator.concatMatrix(1, 0, 0, 1, toPoints(half), 0); drawBorderLine(start.x, starty, end.x, starty + width, true, true, style.getEnumValue(), color); break; case Constants.EN_GROOVE: case Constants.EN_RIDGE: generator.useColor(ColorUtil.lightenColor(color, 0.6f)); moveTo(start.x, starty); lineTo(end.x, starty); lineTo(end.x, starty + 2 * half); lineTo(start.x, starty + 2 * half); closePath(); generator.write(" " + generator.mapCommand("fill")); generator.writeln(" " + generator.mapCommand("newpath")); generator.useColor(color); if (style == RuleStyle.GROOVE) { moveTo(start.x, starty); lineTo(end.x, starty); lineTo(end.x, starty + half); lineTo(start.x + half, starty + half); lineTo(start.x, starty + 2 * half); } else { moveTo(end.x, starty); lineTo(end.x, starty + 2 * half); lineTo(start.x, starty + 2 * half); lineTo(start.x, starty + half); lineTo(end.x - half, starty + half); } closePath(); generator.write(" " + generator.mapCommand("fill")); generator.writeln(" " + generator.mapCommand("newpath")); break; default: throw new UnsupportedOperationException("rule style not supported"); } restoreGraphicsState(); } private static float toPoints(int mpt) { return mpt / 1000f; } /** {@inheritDoc} */ public void moveTo(int x, int y) throws IOException { generator.writeln(generator.formatDouble(toPoints(x)) + " " + generator.formatDouble(toPoints(y)) + " " + generator.mapCommand("moveto")); } /** {@inheritDoc} */ public void lineTo(int x, int y) throws IOException { generator.writeln(generator.formatDouble(toPoints(x)) + " " + generator.formatDouble(toPoints(y)) + " " + generator.mapCommand("lineto")); } /** {@inheritDoc} */ public void arcTo(final double startAngle, final double endAngle, final int cx, final int cy, final int width, final int height) throws IOException { arcToBezierCurveTransformer.arcTo(startAngle, endAngle, cx, cy, width, height); } /** {@inheritDoc} */ public void closePath() throws IOException { generator.writeln("cp"); } private void clipRect(int x, int y, int width, int height) throws IOException { generator.defineRect(toPoints(x), toPoints(y), toPoints(width), toPoints(height)); clip(); } /** {@inheritDoc} */ public void clip() throws IOException { generator.writeln(generator.mapCommand("clip") + " " + generator.mapCommand("newpath")); } /** {@inheritDoc} */ public void saveGraphicsState() throws IOException { generator.saveGraphicsState(); } /** {@inheritDoc} */ public void restoreGraphicsState() throws IOException { generator.restoreGraphicsState(); } /** {@inheritDoc} */ public void rotateCoordinates(double angle) throws IOException { StringBuffer sb = new StringBuffer() .append(generator.formatDouble(angle * 180d / Math.PI)) .append(" rotate "); generator.writeln(sb.toString()); } /** {@inheritDoc} */ public void translateCoordinates(int xTranslate, int yTranslate) throws IOException { StringBuffer sb = new StringBuffer() .append(generator.formatDouble(toPoints(xTranslate))) .append(" ") .append(generator.formatDouble(toPoints(yTranslate))) .append(" translate "); generator.writeln(sb.toString()); } /** {@inheritDoc} */ public void scaleCoordinates(float xScale, float yScale) throws IOException { StringBuffer sb = new StringBuffer() .append(generator.formatDouble(xScale)) .append(" ") .append(generator.formatDouble(yScale)) .append(" scale "); generator.writeln(sb.toString()); } /** {@inheritDoc} */ public void cubicBezierTo(int p1x, int p1y, int p2x, int p2y, int p3x, int p3y) throws IOException { StringBuffer sb = new StringBuffer() .append(generator.formatDouble(toPoints(p1x))) .append(" ") .append(generator.formatDouble(toPoints(p1y))) .append(" ") .append(generator.formatDouble(toPoints(p2x))) .append(" ") .append(generator.formatDouble(toPoints(p2y))) .append(" ") .append(generator.formatDouble(toPoints(p3x))) .append(" ") .append(generator.formatDouble(toPoints(p3y))) .append(" curveto "); generator.writeln(sb.toString()); } }