blob: 17d9108f021e8ed7bbd909de6c49f49add9b36be [file] [log] [blame]
/*
* 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());
}
}
}