/* * $Id: Path.java 341 2012-06-18 14:38:54Z blowagie $ * * This file is part of the iText (R) project. * Copyright (c) 1998-2012 1T3XT BVBA * Authors: VVB, Bruno Lowagie, et al. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License version 3 * as published by the Free Software Foundation with the addition of the * following permission added to Section 15 as permitted in Section 7(a): * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT, * 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * 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 Affero General Public License for more details. * You should have received a copy of the GNU Affero General Public License * along with this program; if not, see http://www.gnu.org/licenses or write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA, 02110-1301 USA, or download the license from the following URL: * http://itextpdf.com/terms-of-use/ * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License. * * In accordance with Section 7(b) of the GNU Affero General Public License, * a covered work must retain the producer line in every PDF that is created * or manipulated using iText. * * You can be released from the requirements of the license by purchasing * a commercial license. Buying such a license is mandatory as soon as you * develop commercial activities involving the iText software without * disclosing the source code of your own applications. * These activities include: offering paid services to customers as an ASP, * serving PDFs on the fly in a web application, shipping iText with a closed * source product. * * For more information, please contact iText Software Corp. at this * address: sales@itextpdf.com */ package com.itextpdf.tool.xml.svg.graphic; import java.util.ArrayList; import java.util.List; import java.util.Map; import com.itextpdf.text.BaseColor; import com.itextpdf.text.pdf.PdfContentByte; import com.itextpdf.tool.xml.svg.PathBean; import com.itextpdf.tool.xml.svg.PathItem; import com.itextpdf.tool.xml.svg.tags.Graphic; import com.itextpdf.tool.xml.svg.utils.EllipseArc; public class Path extends Graphic { PathBean path; public Path(PathBean path, Map<String, String> css) { super(css); this.path = path; } @Override public void draw(PdfContentByte cb) { drawElement(cb); } //just a method that cleans up the path before drawing it List<PathItem> translate(List<PathItem> items){ List<PathItem> result = new ArrayList<PathItem>(); try{ for (PathItem item : items) { if(item.isMoveTo() || item.isLineTo()){ translateForMoveAndLineTo(item, result); } if(item.isHorizontalLineTo() || item.isVerticalLineTo()){ translateForHorizontalOrVerticalLine(item, result); } if(item.isCubicBezier() || item.isQuadraticBezier() || item.isCubicBezierShorthand() || item.isQuadraticBezierShorthand()){ translateCurves(item, result); } if(item.isArcTo()){ translateArc(item, result); } if(item.isClosePath()){ result.add(item); } } }catch(Exception exp){ //when an exception occurs in one of the translate methods, stop looking at the rest of the path and return the list } return result; } private float[] getCurrentPoint(List<PathItem> result){ float[] currentPoint = new float[2]; if(result != null && result.size() != 0){ List<Float> coordinates = result.get(result.size() - 1).getCoordinates(); currentPoint[0] = coordinates.get(coordinates.size() - 2); currentPoint[1] = coordinates.get(coordinates.size() - 1); } return currentPoint; } private void translateArc(PathItem item, List<PathItem> result) throws Exception{ //add the current point at the end int size = 7; //needs groups of 7 coordinates List<Float> numbers = item.getCoordinates(); for (int i = 0; i < (numbers.size() / size); i++) { //first add the current coordinates, copy all and if relative, change the last two coordinates List<Float> coordinates = new ArrayList<Float>(); float[] currentPoint = getCurrentPoint(result); for (int j = 0; j < size; j++) { if(j == size-2 && item.isRelative()){ //x coordinates.add(currentPoint[0] + (numbers.get((i*size)+j))); }else if(j == size-1 && item.isRelative()){ //y coordinates.add(currentPoint[1] + (numbers.get((i*size)+j))); }else{ coordinates.add(numbers.get((i*size)+j)); } } //this is a bit strange but easiest way to transfer the coordinates coordinates.add(currentPoint[0]); coordinates.add(currentPoint[1]); result.add(new PathItem(coordinates, PathItem.ARC)); } if(numbers.size() % size != 0){ throw new Exception("Something wrong with the number of coordinates in the path"); } } private void translateCurves(PathItem item, List<PathItem> result) throws Exception{ int size; if(item.isCubicBezier()){ size = 6; }else if (item.isCubicBezierShorthand() || item.isQuadraticBezier()){ size = 4; }else{ size = 2; } List<Float> numbers = item.getCoordinates(); for (int i = 0; i < (numbers.size() / size); i++) { List<Float> coordinates = new ArrayList<Float>(); //shorthand notations - get the coordinates of the first control point if(item.isCubicBezierShorthand() || item.isQuadraticBezierShorthand()){ //add the last control point of the previous pathItem if(result != null && result.size() != 0){ PathItem previous = result.get(result.size() - 1); if((previous.isCubicBezier() && item.isCubicBezierShorthand()) || (previous.isQuadraticBezier() && item.isQuadraticBezierShorthand())){ List<Float> previousCoordinates = result.get(result.size() - 1).getCoordinates(); float xPreviousControlPoint = previousCoordinates.get(previousCoordinates.size() - 4); float yPreviousControlPoint = previousCoordinates.get(previousCoordinates.size() - 3); //reflection if this point to the currentPoint float current[] = getCurrentPoint(result); coordinates.add(2*current[0] - xPreviousControlPoint); coordinates.add(2*current[1] - yPreviousControlPoint); } } if (coordinates.size() == 0){ //add the currentPoint float current[] = getCurrentPoint(result); coordinates.add(current[0]); coordinates.add(current[1]); } } for (int j = 0; j < size; j++) { //copy the rest of the coordinates if(item.isRelative()){ float[] currentPoint = getCurrentPoint(result); if(j % 2 == 0){ coordinates.add(currentPoint[0] + (numbers.get((i*size)+j))); }else{ coordinates.add(currentPoint[1] + (numbers.get((i*size)+j))); } }else { coordinates.add(numbers.get((i*size)+j)); } } if(item.isCubicBezier() || item.isCubicBezierShorthand()){ result.add(new PathItem(coordinates, PathItem.CUBIC_BEZIER)); }else{ result.add(new PathItem(coordinates, PathItem.QUADRATIC_BEZIER)); } } if(numbers.size() % size != 0){ throw new Exception("Something wrong with the number of coordinates in the path"); } } private void translateForHorizontalOrVerticalLine(PathItem item, List<PathItem> result){ //TODO check if this is correct List<Float> numbers = item.getCoordinates(); if(numbers.size() == 0){ numbers.add(0f); } float[] currentPoint = getCurrentPoint(result); if(!item.isRelative()){ //take the last command List<Float> coordinates = new ArrayList<Float>(); if(item.isHorizontalLineTo()){ coordinates.add(numbers.get(numbers.size() - 1)); coordinates.add(currentPoint[1]); }else{ coordinates.add(currentPoint[0]); coordinates.add(numbers.get(numbers.size() - 1)); } result.add(new PathItem(coordinates, PathItem.LINE)); }else{ float coordinate = 0f; for (int i = 0; i < numbers.size(); i++) { coordinate = coordinate + numbers.get(i); } List<Float> coordinates = new ArrayList<Float>(); if(item.isHorizontalLineTo()){ coordinates.add(coordinate + currentPoint[0]); coordinates.add(currentPoint[1]); }else{ coordinates.add(currentPoint[0]); coordinates.add(coordinate + currentPoint[1]); } result.add(new PathItem(coordinates, PathItem.LINE)); } } private void translateForMoveAndLineTo(PathItem item, List<PathItem> result){ List<Float> numbers = item.getCoordinates(); if(numbers.size() % 2 == 1){ numbers.add(0f); } //for each pair for (int i = 0; i < (numbers.size() / 2); i++) { float x = numbers.get(i*2); float y = numbers.get((i*2)+1); if(item.isRelative()){ float[] currentPoint = getCurrentPoint(result); x = x + currentPoint[0]; y = y + currentPoint[1]; } List<Float> coordinates = new ArrayList<Float>(); coordinates.add(x); coordinates.add(y); if(item.isMoveTo() && i == 0){ result.add(new PathItem(coordinates, PathItem.MOVE)); }else{ result.add(new PathItem(coordinates, PathItem.LINE)); } } } private void drawArc(PdfContentByte cb, List<Float> numbers){ EllipseArc ellipse = EllipseArc.createEllipseArc(numbers.get(7), numbers.get(8), numbers.get(5), numbers.get(6), numbers.get(0), numbers.get(1), numbers.get(4), numbers.get(3)); cb.setColorFill(BaseColor.ORANGE); cb.rectangle(numbers.get(7), numbers.get(8), 2, 2); //p1 cb.fill(); cb.setColorFill(BaseColor.GREEN); cb.rectangle(numbers.get(5), numbers.get(6), 2, 2); //p2 cb.fill(); cb.arc(ellipse.getCx() - numbers.get(0), ellipse.getCy() - numbers.get(1), ellipse.getCx() + numbers.get(0), ellipse.getCy() + numbers.get(1), ellipse.getStartAng(), ellipse.getExtend()); } public List<PathItem> getTranslatedPathItems(){ return translate(path.getPathItems()); } void drawElement(PdfContentByte cb){ try{ List<PathItem> translatedItems = translate(path.getPathItems()); //loop over the items in the path for (PathItem item : translatedItems) { List<Float> numbers = item.getCoordinates(); if(item.isMoveTo()){ cb.moveTo(numbers.get(0), numbers.get(1)); }else if(item.isLineTo()){ cb.lineTo(numbers.get(0), numbers.get(1)); }else if(item.isCubicBezier()){ cb.curveTo(numbers.get(0), numbers.get(1), numbers.get(2), numbers.get(3), numbers.get(4), numbers.get(5)); }else if(item.isQuadraticBezier()){ cb.curveTo(numbers.get(0), numbers.get(1), numbers.get(2), numbers.get(3)); }else if(item.isArcTo()){ drawArc(cb, numbers); }else if(item.isClosePath()){ cb.closePath(); }else{ //System.out.println(item); } } }catch(Exception exp){ //TODO } } }