| /* |
| * 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.pdf; |
| |
| import java.awt.Color; |
| import java.awt.Point; |
| import java.awt.Rectangle; |
| import java.io.IOException; |
| |
| 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; |
| |
| /** |
| * PDF-specific implementation of the {@link GraphicsPainter}. |
| */ |
| public class PDFGraphicsPainter implements GraphicsPainter, BezierCurvePainter { |
| |
| private final PDFContentGeneratorHelper generator; |
| |
| /** Used for drawing arcs since PS does not natively support drawing elliptic curves */ |
| private final ArcToBezierCurveTransformer arcToBezierCurveTransformer; |
| |
| public PDFGraphicsPainter(PDFContentGenerator generator) { |
| this.generator = new PDFContentGeneratorHelper(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) { |
| //TODO lose scale? |
| drawBorderLine2(x1 / 1000f, y1 / 1000f, x2 / 1000f, y2 / 1000f, |
| horz, startOrBefore, style, col); |
| } |
| |
| /** {@inheritDoc} */ |
| private void drawBorderLine2(float x1, float y1, float x2, float y2, boolean horz, |
| boolean startOrBefore, int style, Color col) { |
| float w = x2 - x1; |
| float h = y2 - y1; |
| float colFactor; |
| switch (style) { |
| case Constants.EN_DASHED: |
| generator.setColor(col); |
| if (horz) { |
| float dashedWidth = BorderPainter.dashWidthCalculator(w, h); |
| if (dashedWidth != 0) { |
| float ym = y1 + (h / 2); |
| generator.setDashLine(dashedWidth, dashedWidth * BorderPainter.DASHED_BORDER_SPACE_RATIO) |
| .setLineWidth(h) |
| .strokeLine(x1, ym, x2, ym); |
| } |
| } else { |
| float dashedWidth = BorderPainter.dashWidthCalculator(h, w); |
| if (dashedWidth != 0) { |
| float xm = x1 + (w / 2); |
| generator.setDashLine(dashedWidth, dashedWidth * BorderPainter.DASHED_BORDER_SPACE_RATIO) |
| .setLineWidth(w) |
| .strokeLine(xm, y1, xm, y2); |
| } |
| } |
| break; |
| case Constants.EN_DOTTED: |
| generator.setColor(col).setRoundCap(); |
| if (horz) { |
| float unit = Math.abs(2 * h); |
| int rep = (int) (w / unit); |
| if (rep % 2 == 0) { |
| rep++; |
| } |
| unit = w / rep; |
| float ym = y1 + (h / 2); |
| generator.setDashLine(0, unit) |
| .setLineWidth(h) |
| .strokeLine(x1, ym, x2, ym); |
| } else { |
| float unit = Math.abs(2 * w); |
| int rep = (int) (h / unit); |
| if (rep % 2 == 0) { |
| rep++; |
| } |
| unit = h / rep; |
| float xm = x1 + (w / 2); |
| generator.setDashLine(0, unit) |
| .setLineWidth(w) |
| .strokeLine(xm, y1, xm, y2); |
| } |
| break; |
| case Constants.EN_DOUBLE: |
| generator.setColor(col) |
| .setSolidLine(); |
| if (horz) { |
| float h3 = h / 3; |
| float ym1 = y1 + (h3 / 2); |
| float ym2 = ym1 + h3 + h3; |
| generator.setLineWidth(h3) |
| .strokeLine(x1, ym1, x2, ym1) |
| .strokeLine(x1, ym2, x2, ym2); |
| } else { |
| float w3 = w / 3; |
| float xm1 = x1 + (w3 / 2); |
| float xm2 = xm1 + w3 + w3; |
| generator.setLineWidth(w3) |
| .strokeLine(xm1, y1, xm1, y2) |
| .strokeLine(xm2, y1, xm2, y2); |
| } |
| break; |
| case Constants.EN_GROOVE: |
| case Constants.EN_RIDGE: |
| colFactor = (style == Constants.EN_GROOVE ? 0.4f : -0.4f); |
| generator.setSolidLine(); |
| if (horz) { |
| Color uppercol = ColorUtil.lightenColor(col, -colFactor); |
| Color lowercol = ColorUtil.lightenColor(col, colFactor); |
| float h3 = h / 3; |
| float ym1 = y1 + (h3 / 2); |
| generator.setLineWidth(h3) |
| .setColor(uppercol) |
| .strokeLine(x1, ym1, x2, ym1) |
| .setColor(col) |
| .strokeLine(x1, ym1 + h3, x2, ym1 + h3) |
| .setColor(lowercol) |
| .strokeLine(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; |
| float xm1 = x1 + (w3 / 2); |
| generator.setLineWidth(w3) |
| .setColor(leftcol) |
| .strokeLine(xm1, y1, xm1, y2) |
| .setColor(col) |
| .strokeLine(xm1 + w3, y1, xm1 + w3, y2) |
| .setColor(rightcol) |
| .strokeLine(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); |
| generator.setSolidLine(); |
| Color c = col; |
| if (horz) { |
| c = ColorUtil.lightenColor(c, (startOrBefore ? 1 : -1) * colFactor); |
| float ym1 = y1 + (h / 2); |
| generator.setLineWidth(h) |
| .setColor(c) |
| .strokeLine(x1, ym1, x2, ym1); |
| } else { |
| c = ColorUtil.lightenColor(c, (startOrBefore ? 1 : -1) * colFactor); |
| float xm1 = x1 + (w / 2); |
| generator.setLineWidth(w) |
| .setColor(c) |
| .strokeLine(xm1, y1, xm1, y2); |
| } |
| break; |
| case Constants.EN_HIDDEN: |
| break; |
| default: |
| generator.setColor(col).setSolidLine(); |
| if (horz) { |
| float ym = y1 + (h / 2); |
| generator.setLineWidth(h) |
| .strokeLine(x1, ym, x2, ym); |
| } else { |
| float xm = x1 + (w / 2); |
| generator.setLineWidth(w) |
| .strokeLine(xm, y1, xm, y2); |
| } |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public void drawLine(Point start, Point end, |
| int width, Color color, RuleStyle style) { |
| 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, start.y - half, end.x, end.y + half, |
| true, true, style.getEnumValue(), color); |
| break; |
| case Constants.EN_DOTTED: |
| generator.clipRect(boundingRect) |
| //This displaces the dots to the right by half a dot's width |
| //TODO There's room for improvement here |
| .transformCoordinatesLine(1, 0, 0 , 1, half, 0); |
| drawBorderLine(start.x, start.y - half, end.x, end.y + half, true, true, style.getEnumValue(), |
| color); |
| break; |
| case Constants.EN_GROOVE: |
| case Constants.EN_RIDGE: |
| generator.setFillColor(ColorUtil.lightenColor(color, 0.6f)) |
| .fillRect(start.x, start.y, end.x, starty + 2 * half) |
| .setFillColor(color) |
| .fillRidge(style, start.x, start.y, end.x, end.y, half); |
| break; |
| default: |
| throw new UnsupportedOperationException("rule style not supported"); |
| } |
| restoreGraphicsState(); |
| } |
| |
| private static String format(int coordinate) { |
| //TODO lose scale? |
| return format(coordinate / 1000f); |
| } |
| |
| private static String format(float coordinate) { |
| return PDFContentGenerator.format(coordinate); |
| } |
| |
| /** {@inheritDoc} */ |
| public void moveTo(int x, int y) { |
| generator.moveTo(x, y); |
| } |
| |
| /** {@inheritDoc} */ |
| public void lineTo(int x, int y) { |
| generator.lineTo(x, y); |
| } |
| |
| /** {@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() { |
| generator.closePath(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void clip() { |
| generator.clip(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void saveGraphicsState() { |
| generator.saveGraphicsState(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void restoreGraphicsState() { |
| generator.restoreGraphicsState(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void rotateCoordinates(double angle) throws IOException { |
| float s = (float) Math.sin(angle); |
| float c = (float) Math.cos(angle); |
| generator.transformFloatCoordinates(c, s, -s, c, 0, 0); |
| } |
| |
| /** {@inheritDoc} */ |
| public void translateCoordinates(int xTranslate, int yTranslate) throws IOException { |
| generator.transformCoordinates(1000, 0, 0, 1000, xTranslate, yTranslate); |
| } |
| |
| /** {@inheritDoc} */ |
| public void scaleCoordinates(float xScale, float yScale) throws IOException { |
| generator.transformFloatCoordinates(xScale, 0, 0, yScale, 0, 0); |
| } |
| |
| /** {@inheritDoc} */ |
| public void cubicBezierTo(int p1x, int p1y, int p2x, int p2y, int p3x, int p3y) { |
| generator.cubicBezierTo(p1x, p1y, p2x, p2y, p3x, p3y); |
| } |
| |
| // TODO consider enriching PDFContentGenerator with part of this API |
| private static class PDFContentGeneratorHelper { |
| |
| private final PDFContentGenerator generator; |
| |
| public PDFContentGeneratorHelper(PDFContentGenerator generator) { |
| this.generator = generator; |
| } |
| |
| public PDFContentGeneratorHelper moveTo(int x, int y) { |
| return add("m", format(x), format(y)); |
| } |
| |
| public PDFContentGeneratorHelper lineTo(int x, int y) { |
| return add("l", format(x), format(y)); |
| } |
| |
| /** {@inheritDoc} */ |
| public PDFContentGeneratorHelper cubicBezierTo(int p1x, int p1y, int p2x, int p2y, int p3x, int p3y) { |
| return add("c", format(p1x), format(p1y), format(p2x), format(p2y), format(p3x), format(p3y)); |
| } |
| |
| public PDFContentGeneratorHelper closePath() { |
| return add("h"); |
| } |
| |
| public PDFContentGeneratorHelper clip() { |
| return addLine("W\nn"); |
| } |
| |
| public PDFContentGeneratorHelper clipRect(Rectangle rectangle) { |
| generator.clipRect(rectangle); |
| return this; |
| } |
| |
| public PDFContentGeneratorHelper saveGraphicsState() { |
| return addLine("q"); |
| } |
| |
| public PDFContentGeneratorHelper restoreGraphicsState() { |
| return addLine("Q"); |
| } |
| |
| public PDFContentGeneratorHelper setSolidLine() { |
| generator.add("[] 0 d "); |
| return this; |
| } |
| |
| public PDFContentGeneratorHelper setRoundCap() { |
| return add("J", "1"); |
| } |
| |
| public PDFContentGeneratorHelper strokeLine(float xStart, float yStart, float xEnd, float yEnd) { |
| add("m", xStart, yStart); |
| return addLine("l S", xEnd, yEnd); |
| } |
| |
| public PDFContentGeneratorHelper fillRect(int xStart, int yStart, int xEnd, int yEnd) { |
| String xS = format(xStart); |
| String xE = format(xEnd); |
| String yS = format(yStart); |
| String yE = format(yEnd); |
| return addLine("m", xS, yS) |
| .addLine("l", xE, yS) |
| .addLine("l", xE, yE) |
| .addLine("l", xS, yE) |
| .addLine("h") |
| .addLine("f"); |
| } |
| |
| public PDFContentGeneratorHelper fillRidge(RuleStyle style, int xStart, int yStart, int xEnd, |
| int yEnd, int half) { |
| String xS = format(xStart); |
| String xE = format(xEnd); |
| String yS = format(yStart); |
| if (style == RuleStyle.GROOVE) { |
| addLine("m", xS, yS) |
| .addLine("l", xE, yS) |
| .addLine("l", xE, format(yStart + half)) |
| .addLine("l", format(xStart + half), format(yStart + half)) |
| .addLine("l", xS, format(yStart + 2 * half)); |
| } else { |
| addLine("m", xE, yS) |
| .addLine("l", xE, format(yStart + 2 * half)) |
| .addLine("l", xS, format(yStart + 2 * half)) |
| .addLine("l", xS, format(yStart + half)) |
| .addLine("l", format(xEnd - half), format(yStart + half)); |
| } |
| return addLine("h").addLine("f"); |
| } |
| |
| public PDFContentGeneratorHelper setLineWidth(float width) { |
| return addLine("w", width); |
| } |
| |
| public PDFContentGeneratorHelper setDashLine(float first, float... rest) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("[").append(format(first)); |
| for (float unit : rest) { |
| sb.append(" ").append(format(unit)); |
| } |
| sb.append("] 0 d "); |
| generator.add(sb.toString()); |
| return this; |
| } |
| |
| public PDFContentGeneratorHelper setColor(Color col) { |
| generator.setColor(col, false); |
| return this; |
| } |
| |
| public PDFContentGeneratorHelper setFillColor(Color col) { |
| generator.setColor(col, true); |
| return this; |
| } |
| |
| public PDFContentGeneratorHelper transformFloatCoordinates(float a, float b, float c, float d, |
| float e, float f) { |
| return add("cm", a, b, c, d, e, f); |
| } |
| |
| public PDFContentGeneratorHelper transformCoordinates(int a, int b, int c, int d, int e, int f) { |
| return add("cm", format(a), format(b), format(c), format(d), format(e), format(f)); |
| } |
| |
| public PDFContentGeneratorHelper transformCoordinatesLine(int a, int b, int c, int d, int e, int f) { |
| return addLine("cm", format(a), format(b), format(c), format(d), format(e), format(f)); |
| } |
| |
| public PDFContentGeneratorHelper add(String op) { |
| assert op.equals(op.trim()); |
| generator.add(op + " "); |
| return this; |
| } |
| |
| private PDFContentGeneratorHelper add(String op, String... args) { |
| add(createArgs(args), op); |
| return this; |
| } |
| |
| public PDFContentGeneratorHelper addLine(String op) { |
| assert op.equals(op.trim()); |
| generator.add(op + "\n"); |
| return this; |
| } |
| |
| public PDFContentGeneratorHelper addLine(String op, String... args) { |
| addLine(createArgs(args), op); |
| return this; |
| } |
| |
| private PDFContentGeneratorHelper add(String op, float... args) { |
| add(createArgs(args), op); |
| return this; |
| } |
| |
| public PDFContentGeneratorHelper addLine(String op, float... args) { |
| addLine(createArgs(args), op); |
| return this; |
| } |
| |
| private StringBuilder createArgs(float... args) { |
| StringBuilder sb = new StringBuilder(); |
| for (float arg : args) { |
| sb.append(format(arg)).append(" "); |
| } |
| return sb; |
| } |
| |
| private StringBuilder createArgs(String... args) { |
| StringBuilder sb = new StringBuilder(); |
| for (String arg : args) { |
| sb.append(arg).append(" "); |
| } |
| return sb; |
| } |
| |
| private void add(StringBuilder args, String op) { |
| assert op.equals(op.trim()); |
| generator.add(args.append(op).append(" ").toString()); |
| } |
| |
| private void addLine(StringBuilder args, String op) { |
| assert op.equals(op.trim()); |
| generator.add(args.append(op).append("\n").toString()); |
| } |
| } |
| |
| } |