/*
* Copyright 2006-2017 ICEsoft Technologies Canada Corp.
*
* Licensed 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.
*/
package org.icepdf.core.pobjects.graphics.commands;
import org.icepdf.core.pobjects.LiteralStringObject;
import org.icepdf.core.pobjects.graphics.TextSprite;
import org.icepdf.core.pobjects.graphics.text.GlyphText;
import org.icepdf.core.util.PdfOps;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.PathIterator;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* The PostScriptEncoder is responsible for converting an ArrayList<DrawCmd>
* into postscript operands. Basically the reverse of what the content
* parser does.
* <br>
* NOTE: this is currently a partial implementation to vac
*
* @since 5.0
*/
public class PostScriptEncoder {
private static final Logger logger =
Logger.getLogger(PostScriptEncoder.class.toString());
private static final String SPACE = " ";
private static final String NEWLINE = "\r\n";
private static final String TRUE = "true";
private static final String FALSE = "false";
private static final String NAME = "/";
private static final String BEGIN_ARRAY = "[";
private static final String END_ARRAY = "]";
private static final String BEGIN_STRING = "(";
private static final String END_STRING = ")";
private PostScriptEncoder() {
}
/**
* Processes the given DrawCmd objects and generates the PostScript to
* draw simple shapes and text.
*
* @param drawCmds commands to convert to postscript.
* @return byte[] of PostScript notation.
*/
public static byte[] generatePostScript(ArrayList<DrawCmd> drawCmds) {
StringBuilder postScript = new StringBuilder();
Color color = null;
Shape currentShape = null;
if (logger.isLoggable(Level.FINEST)) {
if (drawCmds != null) {
logger.finest("PostScriptEncoder processing: " +
drawCmds.size() + " commands.");
} else {
logger.finest("PostScriptEncoder does not have any shapes to process. ");
}
}
try {
for (DrawCmd drawCmd : drawCmds) {
// setup an affine transform
if (drawCmd instanceof TransformDrawCmd) {
AffineTransform af = ((TransformDrawCmd) drawCmd).getAffineTransform();
postScript.append(af.getScaleX()).append(SPACE)
.append(af.getShearX()).append(SPACE)
.append(af.getShearY()).append(SPACE)
.append(af.getScaleY()).append(SPACE)
.append(af.getTranslateX()).append(SPACE)
.append(af.getTranslateY()).append(SPACE)
.append(PdfOps.cm_TOKEN).append(NEWLINE);
} else if (drawCmd instanceof TextTransformDrawCmd) {
AffineTransform af = ((TransformDrawCmd) drawCmd).getAffineTransform();
postScript.append(af.getScaleX()).append(SPACE)
.append(af.getShearX()).append(SPACE)
.append(af.getShearY()).append(SPACE)
.append(af.getScaleY()).append(SPACE)
.append(af.getTranslateX()).append(SPACE)
.append(af.getTranslateY()).append(SPACE)
.append(PdfOps.Tm_TOKEN).append(NEWLINE);
}
// reference the colour, we'll decide later if its fill or stroke.
else if (drawCmd instanceof ColorDrawCmd) {
color = ((ColorDrawCmd) drawCmd).getColor();
}
// stroke the shape.
else if (drawCmd instanceof DrawDrawCmd) {
if (color != null) {
float[] colors = color.getRGBColorComponents(null);
// set the stroke color
postScript.append(colors[0]).append(SPACE)
.append(colors[1]).append(SPACE)
.append(colors[2]).append(SPACE)
.append(PdfOps.RG_TOKEN).append(NEWLINE);
// generate the draw operands for current shape.
generateShapePostScript(currentShape, postScript);
// add the fill
postScript.append(PdfOps.S_TOKEN).append(NEWLINE);
}
}
// fill the shape.
else if (drawCmd instanceof FillDrawCmd) {
if (color != null) {
float[] colors = color.getRGBColorComponents(null);
// set fill color
postScript.append(colors[0]).append(SPACE)
.append(colors[1]).append(SPACE)
.append(colors[2]).append(SPACE)
.append(PdfOps.rg_TOKEN).append(NEWLINE);
// generate the draw operands for the current shape.
generateShapePostScript(currentShape, postScript);
// add the fill
postScript.append(PdfOps.f_TOKEN).append(SPACE);
}
}
// current shape.
else if (drawCmd instanceof ShapeDrawCmd) {
currentShape = ((ShapeDrawCmd) drawCmd).getShape();
}
// Sets the stroke.
else if (drawCmd instanceof StrokeDrawCmd) {
BasicStroke stroke = (BasicStroke) ((StrokeDrawCmd) drawCmd).getStroke();
postScript.append(
// line width
stroke.getLineWidth()).append(SPACE)
.append(PdfOps.w_TOKEN).append(SPACE);
// dash phase
float[] dashes = stroke.getDashArray();
postScript.append(BEGIN_ARRAY);
if (dashes != null) {
for (int i = 0, max = dashes.length; i < max; i++) {
postScript.append(dashes[i]);
if (i < max - 1) {
postScript.append(SPACE);
}
}
}
postScript.append(END_ARRAY).append(SPACE);
postScript.append(stroke.getDashPhase()).append(SPACE)
.append(PdfOps.d_TOKEN).append(SPACE);
// cap butt
if (stroke.getEndCap() == BasicStroke.CAP_BUTT) {
postScript.append(0).append(SPACE)
.append(PdfOps.J_TOKEN).append(SPACE);
} else if (stroke.getEndCap() == BasicStroke.CAP_ROUND) {
postScript.append(1).append(SPACE)
.append(PdfOps.J_TOKEN).append(SPACE);
} else if (stroke.getEndCap() == BasicStroke.CAP_SQUARE) {
postScript.append(2).append(SPACE)
.append(PdfOps.J_TOKEN).append(SPACE);
}
// miter join.
if (stroke.getMiterLimit() == BasicStroke.JOIN_MITER) {
postScript.append(0).append(SPACE)
.append(PdfOps.j_TOKEN).append(SPACE);
} else if (stroke.getMiterLimit() == BasicStroke.JOIN_ROUND) {
postScript.append(1).append(SPACE)
.append(PdfOps.j_TOKEN).append(SPACE);
} else if (stroke.getMiterLimit() == BasicStroke.JOIN_BEVEL) {
postScript.append(2).append(SPACE)
.append(PdfOps.j_TOKEN).append(SPACE);
}
postScript.append(NEWLINE);
}
// graphics state setup
else if (drawCmd instanceof GraphicsStateCmd) {
postScript.append('/')
.append(((GraphicsStateCmd) drawCmd).getGraphicStateName()).append(SPACE)
.append(PdfOps.gs_TOKEN).append(SPACE);
}
// break out a text block and child paint operands.
else if (drawCmd instanceof TextSpriteDrawCmd) {
postScript.append(PdfOps.BT_TOKEN).append(NEWLINE);
TextSpriteDrawCmd textSpriteDrawCmd = (TextSpriteDrawCmd) drawCmd;
TextSprite textSprite = textSpriteDrawCmd.getTextSprite();
ArrayList<GlyphText> glyphTexts = textSprite.getGlyphSprites();
if (glyphTexts.size() > 0) {
// write out stat of text paint
postScript.append("1 0 0 -1 ")
.append(glyphTexts.get(0).getX()).append(SPACE)
.append(glyphTexts.get(0).getY()).append(SPACE).append(PdfOps.Tm_TOKEN).append(NEWLINE);
// write out font
postScript.append("/").append(textSprite.getFontName()).append(SPACE)
.append(textSprite.getFontSize()).append(SPACE).append(PdfOps.Tf_TOKEN).append(NEWLINE);
// set the colour
float[] colors = textSprite.getStrokeColor().getRGBColorComponents(null);
// set fill color
postScript.append(colors[0]).append(SPACE)
.append(colors[1]).append(SPACE)
.append(colors[2]).append(SPACE)
.append(PdfOps.rg_TOKEN).append(NEWLINE);
float y = glyphTexts.get(0).getY();
StringBuilder line = new StringBuilder();
GlyphText glyphText;
for (int i = 0, max = glyphTexts.size(); i < max; i++) {
glyphText = glyphTexts.get(i);
// write out the line
if (y != glyphText.getY() || i == max - 1) {
// make sure we write out the last character.
if (i == max - 1) {
line.append(glyphText.getUnicode());
}
postScript.append(BEGIN_ARRAY).append(BEGIN_STRING)
// use literal string to make sure string is escaped correctly
.append(new LiteralStringObject(line.toString()).toString())
.append(END_STRING)
.append(END_ARRAY).append(SPACE)
.append(PdfOps.TJ_TOKEN).append(NEWLINE);
// add shift if newline
postScript.append(0).append(SPACE).append(y - glyphText.getY())
.append(SPACE).append(PdfOps.Td_TOKEN).append(NEWLINE);
// update the current.
y = glyphText.getY();
line = new StringBuilder();
}
line.append(glyphText.getUnicode());
}
postScript.append(PdfOps.ET_TOKEN).append(NEWLINE);
}
}
}
} catch (Throwable e) {
logger.log(Level.WARNING, "Error encoding PostScript notation ", e);
}
if (logger.isLoggable(Level.FINER)) {
logger.finer("PostEncoding: " + postScript.toString());
}
return postScript.toString().getBytes();
}
/**
* Utility to create postscript draw operations from a shapes path
* iterator.
*
* @param currentShape shape to build out draw commands.
* @param postScript string to append draw operands to.
*/
private static void generateShapePostScript(Shape currentShape, StringBuilder postScript) {
PathIterator pathIterator = currentShape.getPathIterator(null);
float[] segment = new float[6];
int segmentType;
while (!pathIterator.isDone()) {
segmentType = pathIterator.currentSegment(segment);
switch (segmentType) {
case PathIterator.SEG_MOVETO:
postScript.append(segment[0]).append(SPACE)
.append(segment[1]).append(SPACE)
.append(PdfOps.m_TOKEN).append(NEWLINE);
break;
case PathIterator.SEG_LINETO:
postScript.append(segment[0]).append(SPACE)
.append(segment[1]).append(SPACE)
.append(PdfOps.l_TOKEN).append(NEWLINE);
break;
case PathIterator.SEG_QUADTO:
postScript.append(segment[0]).append(SPACE)
.append(segment[1]).append(SPACE)
.append(segment[2]).append(SPACE)
.append(segment[3]).append(SPACE)
.append(PdfOps.v_TOKEN).append(NEWLINE);
break;
case PathIterator.SEG_CUBICTO:
postScript.append(segment[0]).append(SPACE)
.append(segment[1]).append(SPACE)
.append(segment[2]).append(SPACE)
.append(segment[3]).append(SPACE)
.append(segment[4]).append(SPACE)
.append(segment[5]).append(SPACE)
.append(PdfOps.c_TOKEN).append(NEWLINE);
break;
case PathIterator.SEG_CLOSE:
postScript.append(PdfOps.h_TOKEN).append(SPACE);
break;
}
pathIterator.next();
}
}
}