/*
* $Id: TextPathGroup.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.awt.geom.AffineTransform;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Element;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfTemplate;
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 TextPathGroup extends Graphic {
final List<Element> list;
final float width, height, x, y;
Path path;
final static int fontsize = 40;
public TextPathGroup(List<Element> list, float x, float y, float width, float height, Map<String, String> css, Path path) {
super(css);
this.list = list;
this.width = width;
this.height = height;
this.x = x;
this.y = y;
this.path = path;
}
void drawGroup(PdfContentByte cb) throws Exception{
draw(cb, pathToLineSegment(), list);
}
public void draw(PdfContentByte cb, List<float[]> lines, List<Element> list) throws Exception{
if(lines.size() < 2){
return; //Do nothing
}
//TODO check all the style elements
cb.setLineWidth(3);
//first set a letter type
BaseFont bf = BaseFont.createFont("c:/windows/fonts/arial.ttf", BaseFont.WINANSI, BaseFont.EMBEDDED);
cb.setFontAndSize(bf, fontsize);
float xPrevious = lines.get(0)[0], yPrevious = lines.get(0)[1];
int indexOfTextElement = 0, indexOfCharacter = 0;
String currentCharacter = getCharacter(indexOfTextElement, indexOfCharacter);
while(currentCharacter == null || currentCharacter.length() == 0){
if(indexOfCharacter >= getLengthOfText(indexOfTextElement) - 1){ //this was the last character of the text element
if(indexOfTextElement == list.size() - 1){ //this was the last text element; exit while loop
return; //stop
}else{ //goto first character of the next element
//TODO set the css
indexOfTextElement++;
indexOfCharacter = 0;
}
}
currentCharacter = getCharacter(indexOfTextElement, indexOfCharacter);
}
double halfWidthOfCharacter = cb.getEffectiveStringWidth(currentCharacter, true) / 2.0;
double totalLength = 0;
boolean lookForStart = true;
for (int j = 1; j < lines.size(); j++) {
float[] point = lines.get(j);
double lengthLijnStuk = calculateDistance(xPrevious, yPrevious, point[0], point[1]);
//System.out.println(lengthLijnStuk);
totalLength = totalLength + lengthLijnStuk;
//System.out.println(totalLength);
while(totalLength >= halfWidthOfCharacter){
double tussen = totalLength - halfWidthOfCharacter;
double xyAlongThis = lengthLijnStuk - tussen - halfWidthOfCharacter;
double xy[] = getPointOnLine(xPrevious, yPrevious, point[0], point[1], xyAlongThis);
if(lookForStart){
showText(cb, point[0], point[1], xPrevious, yPrevious, xy[0], xy[1], currentCharacter);
lookForStart = false;
totalLength = tussen; //distance to the end of the line segment
}else{
//look for the end point
lookForStart = true;
totalLength = tussen; //distance to the end of the line segment
indexOfCharacter++;
currentCharacter = getCharacter(indexOfTextElement, indexOfCharacter);
while(currentCharacter == null || currentCharacter.length() == 0){
if(indexOfCharacter >= getLengthOfText(indexOfTextElement) - 1){ //this was the last character of the text element
if(indexOfTextElement == list.size() - 1){ //this was the last text element; exit while loop
return;
}else{ //goto first character of the next element
indexOfTextElement++;
indexOfCharacter = 0;
}
}
currentCharacter = getCharacter(indexOfTextElement, indexOfCharacter);
}
halfWidthOfCharacter = cb.getEffectiveStringWidth(currentCharacter, true) / 2.0;
}
}
xPrevious = point[0];
yPrevious = point[1];
}
}
private String getCharacter(int indexOfTextElement, int indexOfCharacter){
Text text = (Text)list.get(indexOfTextElement);
if(text != null && text.getText().length() > indexOfCharacter) {
return ""+text.getText().charAt(indexOfCharacter);
}
return null;
}
private int getLengthOfText(int indexOfTextElement){
Text text = (Text)list.get(indexOfTextElement);
return text.getText().length();
}
public void drawLine(PdfContentByte cb, float x1, float y1, float x2, float y2, int step) throws Exception{
cb.moveTo(x1, y1);
cb.lineTo(x2, y2);
cb.stroke();
for (int i = 0; i < 10; i++) {
double point[] = getPointOnLine(x1, y1, x2, y2, i*step);
cb.rectangle((float)point[0], (float)point[1], 2, 2);
cb.stroke();
}
}
private void showText(PdfContentByte cb, float x, float y, float xPrevious, float yPrevious, double xmidden, double ymidden, String character) throws Exception{
double corner = calculateCorner(x, y, xPrevious, yPrevious);
cb.saveState();
PdfTemplate template2 = cb.createTemplate(1000, 1000);
template2.beginText();
template2.setColorFill(BaseColor.BLACK);
BaseFont bf = BaseFont.createFont();
template2.setFontAndSize(bf, fontsize);
//template2.setTextRise(10);
//double halfWidthOfCharacter = cb.getEffectiveStringWidth(character+"", true) / 2.0;
template2.setTextMatrix(0, 0);
template2.showText(character+"");
template2.endText();
cb.concatCTM(AffineTransform.getTranslateInstance(xmidden, ymidden));
cb.concatCTM(AffineTransform.getRotateInstance(Math.toRadians(corner)));
cb.concatCTM(1, 0, 0, -1, 0, 0);
cb.addTemplate(template2, 0, 0);
cb.restoreState();
}
private double calculateCorner(double x1, double y1, double x0, double y0){
//to center
double x = x1 - x0;
double y = y1 - y0;
if(x >= 0 && y >= 0){
return(Math.toDegrees(Math.atan(y/x)));
}
if(x < 0 && y > 0){
return(Math.toDegrees(Math.atan(y/x)) + 180);
}
if(x < 0 && y < 0){
return(Math.toDegrees(Math.atan(y/x)) + 180);
}
if(x >= 0 && y < 0){
return(Math.toDegrees(Math.atan(y/x)) + 360);
}
return 0;
}
private double[] getPointOnLine(float x1, float y1, float x2, float y2, double length){
double corner = calculateCorner(x2, y2, x1, y1);
//System.out.println(corner);
double rad = Math.toRadians(corner);
double result[] = new double[2];
result[0] = x1 + length * Math.cos(rad);
result[1] = y1 + length * Math.sin(rad);
return result;
}
private double calculateDistance(float x1, float y1, float x2, float y2){
return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
}
private List<float[]> pathToLineSegment(){
return pathToLineSegment(path.getTranslatedPathItems());
}
private List<float[]> pathToLineSegment(List<PathItem> pathItems){
List<float[]> coordinates = new ArrayList<float[]>();
for (PathItem item : pathItems) {
List<Float> numbers = item.getCoordinates();
if(item.isMoveTo() || item.isLineTo()){
float[] point = new float[2];
point[0] = numbers.get(0);
point[1] = numbers.get(1);
coordinates.add(point);
}else if(item.isCubicBezier() || item.isQuadraticBezier()){
coordinates.addAll(bezierCurveToLines(coordinates, item));
}else if(item.isArcTo()){
coordinates.addAll(arcToLines(item));
}
}
return coordinates;
}
private List<float[]> arcToLines(final PathItem item){
List<Float> numbers = item.getCoordinates();
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));
List<float[]> newCoordinates = PdfContentByte.bezierArc(ellipse.getCx() - numbers.get(0), ellipse.getCy() - numbers.get(1), ellipse.getCx() + numbers.get(0), ellipse.getCy() + numbers.get(1),
ellipse.getStartAng(), ellipse.getExtend());
List<float[]> result = new ArrayList<float[]>();
if (newCoordinates.isEmpty()) return result;
float pt[] = newCoordinates.get(0);
float x0 = pt[0];
float y0 = pt[1];
for (int k = 0; k < newCoordinates.size(); ++k) {
pt = newCoordinates.get(k);
result.addAll(bezierCurveToLines(x0, y0, pt[2], pt[3], pt[4], pt[5], pt[6], pt[7], true));
x0 = pt[6];
y0 = pt[7];
}
return result;
}
private List<float[]> bezierCurveToLines(final List<float[]> coordinates, final PathItem item){
List<Float> numbers = item.getCoordinates();
float start[] = coordinates.get(coordinates.size() - 1);
float x0 = start[0];
float y0 = start[1];
float x1 = numbers.get(0);
float y1 = numbers.get(1);
float x2 = numbers.get(2);
float y2 = numbers.get(3);
float x3 = 0;
float y3 = 0;
if(item.isCubicBezier()){
x3 = numbers.get(4);
y3 = numbers.get(5);
}
return bezierCurveToLines(x0, y0, x1, y1, x2, y2, x3, y3, item.isCubicBezier());
}
private List<float[]> bezierCurveToLines(float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, boolean cubic){
float A, B, C, D, E, F, G, H;
if(cubic){
A = x3 - 3 * x2 + 3 * x1 - x0;
B = 3 * x2 - 6 * x1 + 3 * x0;
C = 3 * x1 - 3 * x0;
D = x0;
E = y3 - 3 * y2 + 3 * y1 - y0;
F = 3 * y2 - 6 * y1 + 3 * y0;
G = 3 * y1 - 3 * y0;
H = y0;
}else{
A = 0;
B = x2 - 2 * x1 + x0;
C = 2 * x1 - 2 * x0;
D = x0;
E = 0;
F = y2 - 2 * y1 + y0;
G = 2 * y1 - 2 * y0;
H = y0;
}
List<float[]> result = new ArrayList<float[]>();
float step = 0.005f;
for (float t = step; t <= 1; t += step) {
float[] point = new float[2];
point[0] = A * t * t * t + B * t * t + C * t + D;
point[1] = E * t * t * t + F * t * t + G * t + H;
result.add(point);
}
return result;
}
@Override
public void draw(PdfContentByte cb) {
try{
drawGroup(cb);
}catch(Exception exp){
//System.out.println(exp.getMessage());
}
}
}