PDFBox - Line / Rectangle extraction - java

I am trying to extract text coordinates and line (or rectangle) coordinates from a PDF.
The TextPosition class has getXDirAdj() and getYDirAdj() methods which transform coordinates according to the direction of the text piece the respective TextPosition object represents (Corrected based on comment from #mkl)
The final output is consistent, irrespective of the page rotation.
The coordinates needed on the output are X0,Y0 (TOP LEFT CORNER OF THE PAGE)
This is a slight modification from the solution by #Tilman Hausherr. The y coordinates are inverted (height - y) to keep it consistent with the coordinates from the text extraction process, also the output is written to a csv.
public class LineCatcher extends PDFGraphicsStreamEngine
{
private static final GeneralPath linePath = new GeneralPath();
private static ArrayList<Rectangle2D> rectList= new ArrayList<Rectangle2D>();
private int clipWindingRule = -1;
private static String headerRecord = "Text|Page|x|y|width|height|space|font";
public LineCatcher(PDPage page)
{
super(page);
}
public static void main(String[] args) throws IOException
{
if( args.length != 4 )
{
usage();
}
else
{
PDDocument document = null;
FileOutputStream fop = null;
File file;
Writer osw = null;
int numPages;
double page_height;
try
{
document = PDDocument.load( new File(args[0], args[1]) );
numPages = document.getNumberOfPages();
file = new File(args[2], args[3]);
fop = new FileOutputStream(file);
// if file doesnt exists, then create it
if (!file.exists()) {
file.createNewFile();
}
osw = new OutputStreamWriter(fop, "UTF8");
osw.write(headerRecord + System.lineSeparator());
System.out.println("Line Processing numPages:" + numPages);
for (int n = 0; n < numPages; n++) {
System.out.println("Line Processing page:" + n);
rectList = new ArrayList<Rectangle2D>();
PDPage page = document.getPage(n);
page_height = page.getCropBox().getUpperRightY();
LineCatcher lineCatcher = new LineCatcher(page);
lineCatcher.processPage(page);
try{
for(Rectangle2D rect:rectList) {
String pageNum = Integer.toString(n + 1);
String x = Double.toString(rect.getX());
String y = Double.toString(page_height - rect.getY()) ;
String w = Double.toString(rect.getWidth());
String h = Double.toString(rect.getHeight());
writeToFile(pageNum, x, y, w, h, osw);
}
rectList = null;
page = null;
lineCatcher = null;
}
catch(IOException io){
throw new IOException("Failed to Parse document for line processing. Incorrect document format. Page:" + n);
}
};
}
catch(IOException io){
throw new IOException("Failed to Parse document for line processing. Incorrect document format.");
}
finally
{
if ( osw != null ){
osw.close();
}
if( document != null )
{
document.close();
}
}
}
}
private static void writeToFile(String pageNum, String x, String y, String w, String h, Writer osw) throws IOException {
String c = "^" + "|" +
pageNum + "|" +
x + "|" +
y + "|" +
w + "|" +
h + "|" +
"999" + "|" +
"marker-only";
osw.write(c + System.lineSeparator());
}
#Override
public void appendRectangle(Point2D p0, Point2D p1, Point2D p2, Point2D p3) throws IOException
{
// to ensure that the path is created in the right direction, we have to create
// it by combining single lines instead of creating a simple rectangle
linePath.moveTo((float) p0.getX(), (float) p0.getY());
linePath.lineTo((float) p1.getX(), (float) p1.getY());
linePath.lineTo((float) p2.getX(), (float) p2.getY());
linePath.lineTo((float) p3.getX(), (float) p3.getY());
// close the subpath instead of adding the last line so that a possible set line
// cap style isn't taken into account at the "beginning" of the rectangle
linePath.closePath();
}
#Override
public void drawImage(PDImage pdi) throws IOException
{
}
#Override
public void clip(int windingRule) throws IOException
{
// the clipping path will not be updated until the succeeding painting operator is called
clipWindingRule = windingRule;
}
#Override
public void moveTo(float x, float y) throws IOException
{
linePath.moveTo(x, y);
}
#Override
public void lineTo(float x, float y) throws IOException
{
linePath.lineTo(x, y);
}
#Override
public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) throws IOException
{
linePath.curveTo(x1, y1, x2, y2, x3, y3);
}
#Override
public Point2D getCurrentPoint() throws IOException
{
return linePath.getCurrentPoint();
}
#Override
public void closePath() throws IOException
{
linePath.closePath();
}
#Override
public void endPath() throws IOException
{
if (clipWindingRule != -1)
{
linePath.setWindingRule(clipWindingRule);
getGraphicsState().intersectClippingPath(linePath);
clipWindingRule = -1;
}
linePath.reset();
}
#Override
public void strokePath() throws IOException
{
rectList.add(linePath.getBounds2D());
linePath.reset();
}
#Override
public void fillPath(int windingRule) throws IOException
{
linePath.reset();
}
#Override
public void fillAndStrokePath(int windingRule) throws IOException
{
linePath.reset();
}
#Override
public void shadingFill(COSName cosn) throws IOException
{
}
/**
* This will print the usage for this document.
*/
private static void usage()
{
System.err.println( "Usage: java " + LineCatcher.class.getName() + " <input-pdf>" + " <output-file>");
}
}
Was using the PDFGraphicsStreamEngine class to extract Line and Rectangle coordinates. The coordinates of lines and rectangles do not align with the coordinates of the text
Green: Text
Red: Line coordinates obtained as is
Black: Expected coordinates (Obtained after applying transformation on the output)
Tried the setRotation() method to correct for the rotation before running the line extract. However the results are not consistent.
What are the possible options to get the rotation and get a consistent output of the Line / Rectangle coordinates using PDFBox?

As far as I understand the requirements here, the OP works in a coordinate system with the origin in the upper left corner of the visible page (taking the page rotation into account), x coordinates increasing to the right, y coordinates increasing downwards, and the units being the PDF default user space units (usually 1/72 inch).
In this coordinate system he needs to extract (horizontal or vertical) lines in the form of
coordinates of the left / top end point and
the width / height.
Transforming LineCatcher results
The helper class LineCatcher he got from Tilman, on the other hand, does not take page rotation into account. Furthermore, it returns the bottom end point for vertical lines, not the top end point. Thus, a coordinate transformation has to be applied to of the LineCatcher results.
For this simply replace
for(Rectangle2D rect:rectList) {
String pageNum = Integer.toString(n + 1);
String x = Double.toString(rect.getX());
String y = Double.toString(page_height - rect.getY()) ;
String w = Double.toString(rect.getWidth());
String h = Double.toString(rect.getHeight());
writeToFile(pageNum, x, y, w, h, osw);
}
by
int pageRotation = page.getRotation();
PDRectangle pageCropBox = page.getCropBox();
for(Rectangle2D rect:rectList) {
String pageNum = Integer.toString(n + 1);
String x, y, w, h;
switch(pageRotation) {
case 0:
x = Double.toString(rect.getX() - pageCropBox.getLowerLeftX());
y = Double.toString(pageCropBox.getUpperRightY() - rect.getY() + rect.getHeight());
w = Double.toString(rect.getWidth());
h = Double.toString(rect.getHeight());
break;
case 90:
x = Double.toString(rect.getY() - pageCropBox.getLowerLeftY());
y = Double.toString(rect.getX() - pageCropBox.getLowerLeftX());
w = Double.toString(rect.getHeight());
h = Double.toString(rect.getWidth());
break;
case 180:
x = Double.toString(pageCropBox.getUpperRightX() - rect.getX() - rect.getWidth());
y = Double.toString(rect.getY() - pageCropBox.getLowerLeftY());
w = Double.toString(rect.getWidth());
h = Double.toString(rect.getHeight());
break;
case 270:
x = Double.toString(pageCropBox.getUpperRightY() - rect.getY() + rect.getHeight());
y = Double.toString(pageCropBox.getUpperRightX() - rect.getX() - rect.getWidth());
w = Double.toString(rect.getHeight());
h = Double.toString(rect.getWidth());
break;
default:
throw new IOException(String.format("Unsupported page rotation %d on page %d.", pageRotation, page));
}
writeToFile(pageNum, x, y, w, h, osw);
}
(ExtractLinesWithDir test testExtractLineRotationTestWithDir)
Relation to TextPosition.get?DirAdj() coordinates
The OP describes the coordinates by referring to the TextPosition class methods getXDirAdj() and getYDirAdj(). Indeed, these methods return coordinates in a coordinate system with the origin in the upper left page corner and y coordinates increasing downwards after rotating the page so that the text is drawn upright.
In case of the example document all the text is drawn so that it is upright after applying the page rotation. From this my understanding of the requirement written at the top has been derived.
The problem with using the TextPosition.get?DirAdj() values as coordinates globally, though, is that in documents with pages with text drawn in different directions, the collected text coordinates suddenly are relative to different coordinate systems. Thus, a general solution should not collect coordinates wildly like that. Instead it should determine a page orientation at first (e.g. the orientation given by the page rotation or the orientation shared by most of the text) and use coordinates in the fixed coordinate system given by that orientation plus an indication of the writing direction of the text piece in question.

Related

How to crop pdf according to special chars by PdfBox [duplicate]

i'm trying to extract text with coordinates from a pdf file using PDFBox.
I mixed some methods/info found on internet (stackoverflow too), but the problem i have the coordinates doesnt'seems to be right. When i try to use coordinates for drawing a rectangle on top of tex, for example, the rect is painted elsewhere.
This is my code (please don't judge the style, was written very fast just to test)
TextLine.java
import java.util.List;
import org.apache.pdfbox.text.TextPosition;
/**
*
* #author samue
*/
public class TextLine {
public List<TextPosition> textPositions = null;
public String text = "";
}
myStripper.java
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.pdfbox.text.TextPosition;
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
/**
*
* #author samue
*/
public class myStripper extends PDFTextStripper {
public myStripper() throws IOException
{
}
#Override
protected void startPage(PDPage page) throws IOException
{
startOfLine = true;
super.startPage(page);
}
#Override
protected void writeLineSeparator() throws IOException
{
startOfLine = true;
super.writeLineSeparator();
}
#Override
public String getText(PDDocument doc) throws IOException
{
lines = new ArrayList<TextLine>();
return super.getText(doc);
}
#Override
protected void writeWordSeparator() throws IOException
{
TextLine tmpline = null;
tmpline = lines.get(lines.size() - 1);
tmpline.text += getWordSeparator();
super.writeWordSeparator();
}
#Override
protected void writeString(String text, List<TextPosition> textPositions) throws IOException
{
TextLine tmpline = null;
if (startOfLine) {
tmpline = new TextLine();
tmpline.text = text;
tmpline.textPositions = textPositions;
lines.add(tmpline);
} else {
tmpline = lines.get(lines.size() - 1);
tmpline.text += text;
tmpline.textPositions.addAll(textPositions);
}
if (startOfLine)
{
startOfLine = false;
}
super.writeString(text, textPositions);
}
boolean startOfLine = true;
public ArrayList<TextLine> lines = null;
}
click event on AWT button
private void jButton1MouseClicked(java.awt.event.MouseEvent evt) {
// TODO add your handling code here:
try {
File file = new File("C:\\Users\\samue\\Desktop\\mwb_I_201711.pdf");
PDDocument doc = PDDocument.load(file);
myStripper stripper = new myStripper();
stripper.setStartPage(1); // fix it to first page just to test it
stripper.setEndPage(1);
stripper.getText(doc);
TextLine line = stripper.lines.get(1); // the line i want to paint on
float minx = -1;
float maxx = -1;
for (TextPosition pos: line.textPositions)
{
if (pos == null)
continue;
if (minx == -1 || pos.getTextMatrix().getTranslateX() < minx) {
minx = pos.getTextMatrix().getTranslateX();
}
if (maxx == -1 || pos.getTextMatrix().getTranslateX() > maxx) {
maxx = pos.getTextMatrix().getTranslateX();
}
}
TextPosition firstPosition = line.textPositions.get(0);
TextPosition lastPosition = line.textPositions.get(line.textPositions.size() - 1);
float x = minx;
float y = firstPosition.getTextMatrix().getTranslateY();
float w = (maxx - minx) + lastPosition.getWidth();
float h = lastPosition.getHeightDir();
PDPageContentStream contentStream = new PDPageContentStream(doc, doc.getPage(0), PDPageContentStream.AppendMode.APPEND, false);
contentStream.setNonStrokingColor(Color.RED);
contentStream.addRect(x, y, w, h);
contentStream.fill();
contentStream.close();
File fileout = new File("C:\\Users\\samue\\Desktop\\pdfbox.pdf");
doc.save(fileout);
doc.close();
} catch (Exception ex) {
}
}
any suggestion? what am i doing wrong?
This is just another case of the excessive PdfTextStripper coordinate normalization. Just like you I had thought that by using TextPosition.getTextMatrix() (instead of getX() and getY) one would get the actual coordinates, but no, even these matrix values have to be corrected (at least in PDFBox 2.0.x, I haven't checked 1.8.x) because the matrix is multiplied by a translation making the lower left corner of the crop box the origin.
Thus, in your case (in which the lower left of the crop box is not the origin), you have to correct the values, e.g. by replacing
float x = minx;
float y = firstPosition.getTextMatrix().getTranslateY();
by
PDRectangle cropBox = doc.getPage(0).getCropBox();
float x = minx + cropBox.getLowerLeftX();
float y = firstPosition.getTextMatrix().getTranslateY() + cropBox.getLowerLeftY();
Instead of
you now get
Obviously, though, you will also have to correct the height somewhat. This is due to the way the PdfTextStripper determines the text height:
// 1/2 the bbox is used as the height todo: why?
float glyphHeight = bbox.getHeight() / 2;
(from showGlyph(...) in LegacyPDFStreamEngine, the parent class of PdfTextStripper)
While the font bounding box indeed usually is too large, half of it often is not enough.
The following code worked for me:
// Definition of font baseline, ascent, descent: https://en.wikipedia.org/wiki/Ascender_(typography)
//
// The origin of the text coordinate system is the top-left corner where Y increases downward.
// TextPosition.getX(), getY() return the baseline.
TextPosition firstLetter = textPositions.get(0);
TextPosition lastLetter = textPositions.get(textPositions.size() - 1);
// Looking at LegacyPDFStreamEngine.showGlyph(), ascender and descender heights are calculated like
// CapHeight: https://stackoverflow.com/a/42021225/14731
float ascent = firstLetter.getFont().getFontDescriptor().getAscent() / 1000 * lastLetter.getFontSize();
Point topLeft = new Point(firstLetter.getX(), firstLetter.getY() - ascent);
float descent = lastLetter.getFont().getFontDescriptor().getDescent() / 1000 * lastLetter.getFontSize();
// Descent is negative, so we need to negate it to move downward.
Point bottomRight = new Point(lastLetter.getX() + lastLetter.getWidth(),
lastLetter.getY() - descent);
float descender = lastLetter.getFont().getFontDescriptor().getDescent() / 1000 * lastLetter.getFontSize();
// Descender height is negative, so we need to negate it to move downward
Point bottomRight = new Point(lastLetter.getX() + lastLetter.getWidth(),
lastLetter.getY() - descender);
In other words, we are creating a bounding box from the font's ascender down to its descender.
If you want to render these coordinates with the origin in the bottom-left corner, see https://stackoverflow.com/a/28114320/14731 for more details. You'll need to apply a transform like this:
contents.transform(new Matrix(1, 0, 0, -1, 0, page.getHeight()));

How can i properly bind the rotation property with two other properties?

I would like to draw an arrow in a JavaFX application, that rotates around an object that is bound to it (2 group objects that are connected with an arrow). To compute the correct angle I tried to compute it by substracting the two coordinates (end and start xy coordinates from the groups). But my arrow head will not move. Any ideas what I am doing wrong?
// controller code:
PlaceIcon startIconGroup = new PlaceIcon();
startIconGroup.setLayoutX(100);
startIconGroup.setLayoutY(120);
startIconGroup.addEventHandler(MouseEvent.MOUSE_CLICKED, onClickEventHandler); // for dragging
startIconGroup.addEventHandler(MouseEvent.MOUSE_DRAGGED, onDragEventHandler);
startIconGroup.addEventHandler(MouseEvent.MOUSE_PRESSED, onPressEventHandler);
workplace.getChildren().add(startIconGroup); // workplace is the pane were the nodes are shown
PlaceIcon endIconGroup = new PlaceIcon();
endIconGroup.setLayoutX(100);
endIconGroup.setLayoutY(120);
endIconGroup.addEventHandler(MouseEvent.MOUSE_CLICKED, onClickEventHandler);
endIconGroup.addEventHandler(MouseEvent.MOUSE_DRAGGED, onDragEventHandler);
endIconGroup.addEventHandler(MouseEvent.MOUSE_PRESSED, onPressEventHandler);
endIconGroup.getChildren().add(startIconGroup);
RelationIcon relationIcon = new RelationIcon();
relationIcon.setStartNode(startNode);
relationIcon.setEndNode(endNode);
...
EventHandler<MouseEvent> onDragEventHandler = new EventHandler<MouseEvent>() {
/**
* This method is used to compute the new position of an node. Therefore, the
* position in pixels (x/yLayout) is incremented by coordinate change of mouse
* movement.
*/
#Override
public void handle(MouseEvent event) {
Group clickedElement = (Group) event.getSource();
// Node node = clickedElement.getChildren().get(0);
double offsetX = event.getSceneX() - mousePosition.get().getX();
double offsetY = event.getSceneY() - mousePosition.get().getY();
clickedElement.setLayoutX(clickedElement.getLayoutX() + offsetX);
clickedElement.setLayoutY(clickedElement.getLayoutY() + offsetY);
mousePosition.set(new Point2D(event.getSceneX(), event.getSceneY()));
eventlog.setText("Move "+ clickedElement + " to x=" + clickedElement.getLayoutX() + " y="
+ clickedElement.getLayoutY());
}
};
And here is the PlaceIconClass
public class RelationIcon extends Group {
Line line;
Polygon arrowHead;
Group startNode;
Group endNode;
public RelationIcon() {
//draw arrow line
line = new Line();
// draw arrow head
arrowHead = new Polygon();
arrowHead.getPoints().addAll(new Double[]{
-10.0, -20.0,
10.0, -20.0,
0.0, 0.0});
// merge into a group
this.getChildren().addAll(line, arrowHead);
}
public Group getStartNode() {
return startNode;
}
public void setStartNode(Group startNode) {
this.startNode = startNode;
line.startXProperty().bind(this.startNode.layoutXProperty());
line.startYProperty().bind(this.startNode.layoutYProperty());
}
public Group getEndNode() {
return endNode;
}
public void setEndNode(Group endNode) {
this.endNode = endNode;
line.endXProperty().bind(this.endNode.layoutXProperty());
line.endYProperty().bind(this.endNode.layoutYProperty());
arrowHead.layoutXProperty().bind(this.endNode.layoutXProperty());
arrowHead.layoutYProperty().bind(this.endNode.layoutYProperty());
arrowHead.rotateProperty().bind(this.endNode.layoutXProperty()); // THIS WORKS
arrowHead.rotateProperty().bind(Bindings.createDoubleBinding(() -> {
double x = line.endXProperty().getValue() - line.startXProperty().getValue();
double y = line.endYProperty().getValue() - line.startYProperty().getValue();
System.out.println(x+" "+y);
double a = Math.atan2(x, y);
return Math.toDegrees(a);
})); // THIS WORKS NOT
}
Thank you for your help!

Method to draw a path throws error when invoked

I have an arraylist of points which are drawn onto a canvas. I have made a method (drawLine) which draws a path/line from one point to another according to the points the user clicks on.
The order in which the points are clicked are put into an arraylist called userPath.
The drawLine method then captures the last two values of userPath and stores them in another arraylist called realPath, and draws a line between these two points and can be seen below.
//this class draws a line
public void drawLine(float x, float y)
{
mPath.reset();
if (userPath.size()>=2);
{
realPath.add(userPath.get(userPath.size()-1));
realPath.add(userPath.get(userPath.size()-2));
}
// start point
Point p = realPath.get(mLastPointIndex);
mPath.moveTo(p.x, p.y);
// end point
p = realPath.get(mLastPointIndex + 1);
//this goes through every point in realPath array list
mPath.lineTo(p.x, p.y);
mCanvas.drawPath(mPath, mPaint);
mPath.reset();
++mLastPointIndex;
isPathStarted = false;
}
When I call the method (drawLine(x,y)) however, it throws an error.
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
// this will draw the path
//mContext = this.mContext;
float Cox = event.getX();
float Coy = event.getY();
double CoX = (double) Cox;
double CoY = (double) Coy;
//the euclid method only accepts double numbers therefore the coordinates of the
//points the user is clicking on need to be converted from float to double
Double NearestDistance = 1000.12; //this is hardcoded for the sake of declaring the variable.
Point NearestPoint = null;
for (int i = 0; i < mPoints.size(); i++)
{
Point current = mPoints.get(i);
double xi = current.x;
double yi = current.y;
double dis = Euclid(CoX, CoY, xi, yi);
if (dis < NearestDistance)
{
NearestPoint = current;
NearestDistance = dis;
}
}
String text = "the closest point to where you clicked is: " + NearestPoint + " and the coordinates are: " + NearestPoint.x + ", "+ NearestPoint.y;
Toast.makeText(mContext, text, LENGTH_SHORT).show();
if (userPath.contains(NearestPoint))
{
String pickPoint = "Pick another point";
Toast.makeText(mContext, pickPoint, LENGTH_SHORT).show();
}
else
{
userPath.add(NearestPoint);
Toast.makeText(mContext, userPath + "", LENGTH_SHORT).show();
drawLine(x,y);
//need to call the drawLine method here to draw the line between the last 2 elements in the arraylist. this throws an error!!!
}
invalidate();
break;
}
return true;
}

How to separately texturize (with different texture files) a 3D model?

I have a 3D file made in Blender and exported to java (.OBJ file), which has his textures separated into some files. Inside this .obj file, it has some fields caled USEMTL and the name of its parts (textures files). However, when I draw it at the screen, he only shows the last USEMTL name.
My question is: How can i proceed to make him read and texturize at the correct way? Without lapping other textures?
This is the class that have the .obj loader
public MLoader(String path, Model m) throws IOException {
#SuppressWarnings("resource")
BufferedReader reader = new BufferedReader(new FileReader(new File(path)));
String line;
while ((line = reader.readLine()) != null) {
if (line.startsWith("v ")) {
float
v1 = Float.valueOf(line.split(" ")[1]),
v2 = Float.valueOf(line.split(" ")[2]),
v3 = Float.valueOf(line.split(" ")[3]);
Vector3f v = new Vector3f (v1, v2, v3);
m.vertex.add(v);
} if (line.startsWith("usemtl ")){
String name = String.valueOf(line.split(" ")[1]);
m.nameTexture.add(name);
continue;
} if (line.startsWith("f ")) {
float
v1 = Float.valueOf(line.split(" ")[1].split("/")[0]),
v2 = Float.valueOf(line.split(" ")[2].split("/")[0]),
v3 = Float.valueOf(line.split(" ")[3].split("/")[0]),
n1 = Float.valueOf(line.split(" ")[1].split("/")[1]),
n2 = Float.valueOf(line.split(" ")[2].split("/")[1]),
n3 = Float.valueOf(line.split(" ")[3].split("/")[1]);
Vector3f
v = new Vector3f (v1, v2, v3),
n = new Vector3f (n1, n2, n3);
m.face.add(new Faces(v, n));
}if (line.startsWith("vt ")) {
float
vt1 = Float.valueOf(line.split(" ")[1]),
vt2 = Float.valueOf(line.split(" ")[2]);
Vector2f vt = new Vector2f (vt1, vt2);
m.vertexTexture.add(vt);
}
}
}
As you can see, I created a IF statement, just to get this usemtl stuff (that is inside .obj file) - the texture names (which are into separate files) just to see if I could bind them individually. But I'm having trouble to do that (perhaps, logic isn't in my side). How to proceed?
Other classes:
Texture Class
public class Textures {
public Texture[] tx;
public void setNumTex(int i) {
tx = new Texture[i];
}
public void setTexture(String format, String name, int i) {
try {
tx[i] = TextureLoader.getTexture(format, new FileInputStream(new File("res/Textures/" + name + format)));
} catch (IOException e) {
e.printStackTrace();
}
}
public void texturing(Vector2f ft1, Vector2f ft2, int indexx) {
for (int i=0; i<indexx; i++) {
tx[i].bind();
}
glTexCoord2f(ft1.x, ft1.y);
glTexCoord2f(ft2.x, ft2.y);
}
}
Render Class
public class Renderer {
Model m;
public void loadContent(String objPath) {
//Everithing that is loading is been putting here
m = Model.getModel("res/Models/" + objPath);
m.loadTex();
}
public void render() {
glEnable(GL_SMOOTH);
m.renderModel();
}
}
Model Class
public class Model {
Textures tx;
List<Vector3f> vertex, norms;
List<Vector2f> vertexTexture;
List<Faces> face;
List<String> nameTexture;
String name[];
private int numTex = 0;
Vector2f
t1 = new Vector2f(), t2 = new Vector2f();
Vector3f
v1 = new Vector3f(), v2 = new Vector3f(), v3 = new Vector3f(),
n1 = new Vector3f(), n2 = new Vector3f(), n3 = new Vector3f(),
public Model(String path)throws
LWJGLException, FileNotFoundException, IOException {
vertex = new ArrayList<Vector3f>();
norms = new ArrayList<Vector3f>();
vertexTexture = new ArrayList<Vector2f>();
face = new ArrayList<Faces>();
nameTexture = new ArrayList<String>();
tx = new Textures();
new MLoader(path, this);
}
public static Model getModel(String path, Vector3f position, Vector3f rotation) {
try {
return new Model(path, position, rotation);
} catch (LWJGLException | IOException e) {
e.printStackTrace();
}
return null;
}
public void loadTex() {
name = new String[nameTexture.toArray().length];
tx.setNumTex(nameTexture.toArray().length);
for (int i=0; i<name.length; i++) {
name[i] = nameTexture.get(i);
numTex += 1;
tx.setTexture(".TGA", name[i], i);
}
}
public void renderModel() {
glPushMatrix();
glPolygonMode(GL_FRONT_AND_BACK, GL_DEPTH_BUFFER_BIT);
glEnable(GL_TEXTURE_2D);
glBegin(GL_TRIANGLES);
{
for (Faces f : face) {
v1 = vertex.get((int) f.v.x - 1);
v2 = vertex.get((int) f.v.y - 1);
v3 = vertex.get((int) f.v.z - 1);
n1 = vertex.get((int) f.n.x - 1);
n2 = vertex.get((int) f.n.y - 1);
n3 = vertex.get((int) f.n.z - 1);
t1 = vertexTexture.get((int) f.n.x - 1); //
t2 = vertexTexture.get((int) f.n.y - 1); //
//Vertexes
glVertex3f(v1.x, v1.y-3.4f, v1.z-0.53f);
glVertex3f(v2.x, v2.y-3.4f, v2.z-0.53f);
glVertex3f(v3.x, v3.y-3.4f, v3.z-0.53f);
//Normals
glNormal3f(n1.x, n1.y-3.4f, n1.z-0.53f);
glNormal3f(n2.x, n2.y-3.4f, n2.z-0.53f);
glNormal3f(n3.x, n3.y-3.4f, n3.z-0.53f);
//Texture
tx.texturing(t1, t2, numTex);
//tx.texturing(n1, n2, n3);
}
}
glEnd();
glDisable(GL_TEXTURE_2D);
glPopMatrix();
}
}
I will not put any code as your question is pretty complex for a 2 line solution.But here how it is usually works in a simple 3D engine.Multiple texturing of a mesh implies that the mesh is broken into 2 or more sub-meshes.I am not blender specialist but I am sure it is capable of exporting the mesh with its sub-mesh tree just like Autodesk 3D studio max.Now,when you import such a mesh into your program you should be able to parse all those sub-meshes into separate entities.Every such a sub-mesh has its own vertices,texture coordinates and normals. When issuing the draw call you would iterate over all the sub-meshes one by one and draw each of them in its own draw call.So on on each draw call you should be able also to attach a unique texture to be used for that specific sub-mesh.That's all.Now it is up to you how to design such a system.
Hope it helps.
Okay, I discovered what was the problem.
Here's the solution: At the class MLoader, I had just to put a "distinguisher" at the "usemtl" part. With it, when he is at the "F SECTION", when he returns and adds to "faceArray" thing the face, he just adds more then he needs.
Example:
face.add(1, 2, 3);
faceArray.add(face); //Assuming that the face has his 1, 2, 3 thing
when he goes again to the "f" if, he adds
face.add(4, 5, 6);
faceArray.add(face); //But, this time, when he adds the face, it already had 1, 2, 3,
//so, he's with '1, 2, 3, 4, 5, 6' into him
Here's the changse i've made (just the changes):
Class MLoader:
public MLoader(String path, Model m) throws IOException {
...
while ((line = reader.readLine()) != null) {
...
} if (line.contains("g ")) {
index = 0;
} if (line.startsWith("usemtl ") && index == 0){
String name = String.valueOf(line.split(" ")[1]);
m.nameTexture.add(name);
m.faceArray.add(m.face);
m.face = new ArrayList<Faces>();
index = 1;
} if (line.contains("f ") && index == 1) {
...
}
Class Model:
int numTex = 0;
List<List <Faces>> faceArray;
...
public void loadTex() {
for (String n : nameTexture) {
tx.loadTexture("TGA", n);
numTex ++;
}
}
...
public void renderModel() {
...
glEnable(GL_TEXTURE_2D);
for (int i = 0; i < numTex; i++) {
tx.tx.get(i).bind();
glBegin(GL_TRIANGLES);
{
for (Faces f : faceArray.get(i)) {
...
}
}
glEnd();
}
glDisable(GL_TEXTURE_2D);
...
}

Java write to .pdf with color

I would like to create a .pdf file and then write to it in color. Through consoles, I have been able to do that with ansi escape sequences. For example if I want red I put "\u001b[31m" in front of my string and "\u001b[0m" to remove all the formatting. You can change the background and the foreground. I designed my own useful class around this to help with displaying information and even for a text-based chess project I am still working on.
However, now I would like to write to files with colors. I would like to do a similar thing that I did with the ansi escapes but probably not with escapes.
If I want to create color in a .pdf, how would I do that (without utilizing an external source)? Can someone point me in the right direction?
Here a quick hack of a very very simple PDF creator class which can do nothing more than writing Courier text in color on colored background:
public class SimplePdfCreator
{
/**
* Creates a {#link SimplePdfCreator} instance writing to the
* given {#link OutputStream}.
*/
public SimplePdfCreator(OutputStream os) throws IOException
{
pdfOs = os instanceof BufferedOutputStream ? (BufferedOutputStream) os : new BufferedOutputStream(os);
writeHeader();
fontObjectNr = writeFont();
initPage();
}
/**
* Sets the (fill) color for the upcoming operations on the current page.
*/
public void color(float r, float g, float b)
{
pageBuilder.append(r)
.append(' ')
.append(g)
.append(' ')
.append(b)
.append(" rg\n");
}
/**
* Sets the background (fill) color for the upcoming operations on the current page.
*/
public void backColor(float r, float g, float b)
{
backBuilder.append(r)
.append(' ')
.append(g)
.append(' ')
.append(b)
.append(" rg\n");
}
/**
* Prints the given text on a baseline starting at the given coordinates
* on the current page.
*/
public void print(int x, int y, String string)
{
pageBuilder.append(x - xNow)
.append(' ')
.append(y - yNow)
.append(" Td (")
.append(string)
.append(") Tj\n");
xNow = x;
yNow = y;
fillBack(string);
}
/**
* Prints the given text on the next line on the current page.
*/
public void print(String string)
{
pageBuilder.append("(")
.append(string)
.append(") '\n");
yNow -= leading;
fillBack(string);
}
/**
* Stores the current page with the printed content in the output
* and creates a new one.
*/
public void storePage() throws IOException
{
writePageContent();
initPage();
}
/**
* Returns the width of the given String.
*/
public double getStringWidth(String string)
{
return string.length() * fontSize * .6;
}
/**
* Returns the font size
*/
public int getFontSize()
{
return fontSize;
}
/**
* Returns the leading
*/
public int getLeading()
{
return leading;
}
/**
* Finishes the output writing required data to it and closing the
* target {#link OutputStream}.
*/
public void close() throws IOException
{
int pagesObjectNr = writePages();
int catalogObjectNr = writeCatalog(pagesObjectNr);
long xrefPosition = writeXref();
writeTrailer(catalogObjectNr);
writeFooter(xrefPosition);
pdfOs.close();;
}
//
// helper methods
//
void writeHeader() throws IOException
{
write("%PDF-1.4\n".getBytes(charSet));
write(new byte[]{'%', (byte)128, (byte)129, (byte)130, '\n'});
}
int writeFont() throws IOException
{
return writeObject("<</Type/Font/Subtype/Type1/BaseFont/Courier/Encoding/WinAnsiEncoding>>\n".getBytes(charSet));
}
void initPage()
{
pageBuilder.setLength(0);
backBuilder.setLength(0);
pageBuilder.append("BT/F0 ")
.append(fontSize)
.append(" Tf ")
.append(leading)
.append(" TL 0 g\n");
backBuilder.append("1 g\n");
xNow = 0;
yNow = 0;
}
void fillBack(String string)
{
backBuilder.append(xNow)
.append(' ')
.append(yNow - leading*.2)
.append(' ')
.append(getStringWidth(string))
.append(' ')
.append(leading)
.append(" re f\n");
}
void writePageContent() throws IOException
{
pageBuilder.append("ET\n");
StringBuilder contents = new StringBuilder();
contents.append("<</Length ")
.append(pageBuilder.length() + backBuilder.length())
.append(">>\nstream\n")
.append(backBuilder)
.append(pageBuilder)
.append("\nendstream\n");
int contentsObjectNr = writeObject(contents.toString().getBytes(charSet));
pageContentsObjects.add(contentsObjectNr);
}
int writePages() throws IOException
{
int pagesObjectNrToBe = xref.size() + pageContentsObjects.size() + 1;
StringBuilder pages = new StringBuilder();
pages.append("<</Type /Pages /Count ")
.append(pageContentsObjects.size())
.append("/Kids[");
for (int pageContentObject : pageContentsObjects)
{
int pageObjectNr = writeObject(String.format("<</Type/Page/Parent %s 0 R/Contents %s 0 R>>\n", pagesObjectNrToBe, pageContentObject).getBytes(charSet));
pages.append(pageObjectNr).append(" 0 R ");
}
pages.append("]/Resources<</ProcSet[/PDF/Text]/Font<</F0 ")
.append(fontObjectNr)
.append(" 0 R>>>>/MediaBox[0 0 612 792]>>\n");
return writeObject(pages.toString().getBytes(charSet));
}
int writeCatalog(int pagesObjectNr) throws IOException
{
return writeObject(String.format("<</Type/Catalog/Pages %s 0 R>>\n", pagesObjectNr).getBytes(charSet));
}
long writeXref() throws IOException
{
long xrefPos = position;
byte[] eol = new byte[]{'\n'};
write("xref\n".getBytes(charSet));
write(String.format("0 %s\n", xref.size() + 1).getBytes(charSet));
write("0000000000 65535 f ".getBytes(charSet));
write(eol);
for(long position: xref)
{
write(String.format("%010d 00000 n ", position).getBytes(charSet));
write(eol);
}
return xrefPos;
}
void writeTrailer(int catalogObjectNr) throws IOException
{
write(String.format("trailer\n<</Size %s/Root %s 0 R>>\n", xref.size() + 1, catalogObjectNr).getBytes(charSet));
}
void writeFooter(long xrefPosition) throws IOException
{
write(String.format("startxref\n%s\n%%%%EOF\n", xrefPosition).getBytes(charSet));
}
int writeObject(byte[] bytes) throws IOException
{
int objectNr = startObject();
write(bytes);
endObj();
return objectNr;
}
int startObject() throws IOException
{
xref.add(position);
int objectNr = xref.size();
write(String.format("%s 0 obj\n", objectNr).getBytes(charSet));
return objectNr;
}
void endObj() throws IOException
{
write("endobj\n".getBytes(charSet));
}
long write(byte[] bytes) throws IOException
{
if (bytes != null)
{
pdfOs.write(bytes);
position += bytes.length;
}
return position;
}
final BufferedOutputStream pdfOs;
final Charset charSet = Charset.forName("ISO-8859-1");
final List<Long> xref = new ArrayList<Long>();
final List<Integer> pageContentsObjects = new ArrayList<Integer>();
final StringBuilder pageBuilder = new StringBuilder();
final StringBuilder backBuilder = new StringBuilder();
final int fontObjectNr;
long position = 0;
int xNow = 0;
int yNow = 0;
int fontSize = 11;
int leading = 11;
}
You can use it like this:
public void test() throws IOException
{
SimplePdfCreator creator = new SimplePdfCreator(new FileOutputStream("target/test-outputs/SimpleGenerated.pdf"));
creator.print(100, 500, "Test line 1");
creator.print("Test line 2");
creator.color(1, 0, 0);
creator.backColor(0, 1, 1);
creator.print(100, 450, "Test line red");
creator.color(0, 1, 0);
creator.backColor(1, 0, 1);
creator.print("Test line green");
creator.color(0, 0, 1);
creator.backColor(1, 1, 0);
creator.print("Test line blue");
creator.color(1, 1, 1);
creator.backColor(0, 0, 0);
creator.print(100, 400, "step");
creator.print(100 + (int)creator.getStringWidth("step"), 400 - creator.getLeading(), "by");
creator.print(100 + (int)creator.getStringWidth("stepby"), 400 - 2 * creator.getLeading(), "step");
creator.storePage();
creator.print(100, 400, "Page 2");
creator.storePage();
creator.close();
}
Which creates this:
And remember, as mentioned above, this is a quick hack, maybe a prove of concept, and there is much to improve, e.g. coordinates probably should be double instead of int, strings shall be escaped before adding to the content (especially concerning brackets, ...

Categories

Resources