Általában
A PDF specifikációja lehetővé teszi, hogy a PDF számos oldalhatárt adjon meg, lásd ezt a választ. Ezeken kívül a tartalmi határok csak az oldal tartalmából származtathatók, pl. tól től
Bármelyik megtalálásához elemezni kell az oldal tartalmát, meg kell keresni a megfelelő műveleteket, és ki kell számítani az eredményül kapott határokat.
Az OP esetében
Minden PDF-minta csak egy oldalhatárt határoz meg, a MediaBox-ot. Így az összes többi PDF-oldalhatár (CropBox, BleedBox, TrimBox, ArtBox) ez az alapértelmezett. Így nem csoda, hogy a próbálkozásaidban
mindegyik (PDRectangle) az oldal teljes magasságát adja vissza: 842
Egyik sem tartalmaz XObject-et, de mindkettő vágógörbét használ.
Teszt-pdf4.pdf esetén:
Start at: 28.31999969482422, 813.6799926757812
Line to: 565.9199829101562, 813.6799926757812
Line to: 565.9199829101562, 660.2196655273438
Line to: 28.31999969482422, 660.2196655273438
Line to: 28.31999969482422, 813.6799926757812
(Ez egyezhet a kérdésében szereplő vázlattal.)
Teszt-pdf5.pdf esetén:
Start at: 23.0, 34.0
Line to: 572.0, 34.0
Line to: 572.0, -751.0
Line to: 23.0, -751.0
Line to: 23.0, 34.0
és
Start at: 23.0, 819.0
Line to: 572.0, 819.0
Line to: 572.0, 34.0
Line to: 23.0, 34.0
Line to: 23.0, 819.0
A vázlattal való egyezés miatt azt feltételezném, hogy az Illustrator mindent megrajzoltnak tekint, miközben egy nem triviális vágógörbe van érvényben, egy összetett elem a vágógörbe szegélyként.
Vágógörbe keresése a PDFBox segítségével
A PDFBox segítségével megtaláltam a fent említett vágógörbéket. A fejlesztés alatt álló 2.0.0-s verzió jelenlegi SNAPSHOT-ját használtam, mivel a szükséges API-k sokat javultak a jelenlegi 1.8.8-as verzióhoz képest.
Kibővítettem PDFGraphicsStreamEngine
egy ClipPathFinder
osztályra:
public class ClipPathFinder extends PDFGraphicsStreamEngine implements Iterable<Path>
{
public ClipPathFinder(PDPage page)
{
super(page);
}
//
// PDFGraphicsStreamEngine overrides
//
public void findClipPaths() throws IOException
{
processPage(getPage());
}
@Override
public void appendRectangle(Point2D p0, Point2D p1, Point2D p2, Point2D p3) throws IOException
{
startPathIfNecessary();
currentPath.appendRectangle(toFloat(p0), toFloat(p1), toFloat(p2), toFloat(p3));
}
@Override
public void drawImage(PDImage pdImage) throws IOException { }
@Override
public void clip(int windingRule) throws IOException
{
currentPath.complete(windingRule);
paths.add(currentPath);
currentPath = null;
}
@Override
public void moveTo(float x, float y) throws IOException
{
startPathIfNecessary();
currentPath.moveTo(x, y);
}
@Override
public void lineTo(float x, float y) throws IOException
{
currentPath.lineTo(x, y);
}
@Override
public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) throws IOException
{
currentPath.curveTo(x1, y1, x2, y2, x3, y3);
}
@Override
public Point2D.Float getCurrentPoint() throws IOException
{
return currentPath.getCurrentPoint();
}
@Override
public void closePath() throws IOException
{
currentPath.closePath();
}
@Override
public void endPath() throws IOException
{
currentPath = null;
}
@Override
public void strokePath() throws IOException
{
currentPath = null;
}
@Override
public void fillPath(int windingRule) throws IOException
{
currentPath = null;
}
@Override
public void fillAndStrokePath(int windingRule) throws IOException
{
currentPath = null;
}
@Override
public void shadingFill(COSName shadingName) throws IOException
{
currentPath = null;
}
void startPathIfNecessary()
{
if (currentPath == null)
currentPath = new Path();
}
Point2D.Float toFloat(Point2D p)
{
if (p == null || (p instanceof Point2D.Float))
{
return (Point2D.Float)p;
}
return new Point2D.Float((float)p.getX(), (float)p.getY());
}
//
// Iterable<Path> implementation
//
public Iterator<Path> iterator()
{
return paths.iterator();
}
Path currentPath = null;
final List<Path> paths = new ArrayList<Path>();
}
Ezt a helper osztályt használja az elérési utak ábrázolására:
public class Path implements Iterable<Path.SubPath>
{
public static class Segment
{
Segment(Point2D.Float start, Point2D.Float end)
{
this.start = start;
this.end = end;
}
public Point2D.Float getStart()
{
return start;
}
public Point2D.Float getEnd()
{
return end;
}
final Point2D.Float start, end;
}
public class SubPath implements Iterable<Segment>
{
public class Line extends Segment
{
Line(Point2D.Float start, Point2D.Float end)
{
super(start, end);
}
//
// Object override
//
@Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append(" Line to: ")
.append(end.getX())
.append(", ")
.append(end.getY())
.append('\n');
return builder.toString();
}
}
public class Curve extends Segment
{
Curve(Point2D.Float start, Point2D.Float control1, Point2D.Float control2, Point2D.Float end)
{
super(start, end);
this.control1 = control1;
this.control2 = control2;
}
public Point2D getControl1()
{
return control1;
}
public Point2D getControl2()
{
return control2;
}
//
// Object override
//
@Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append(" Curve to: ")
.append(end.getX())
.append(", ")
.append(end.getY())
.append(" with Control1: ")
.append(control1.getX())
.append(", ")
.append(control1.getY())
.append(" and Control2: ")
.append(control2.getX())
.append(", ")
.append(control2.getY())
.append('\n');
return builder.toString();
}
final Point2D control1, control2;
}
SubPath(Point2D.Float start)
{
this.start = start;
currentPoint = start;
}
public Point2D getStart()
{
return start;
}
void lineTo(float x, float y)
{
Point2D.Float end = new Point2D.Float(x, y);
segments.add(new Line(currentPoint, end));
currentPoint = end;
}
void curveTo(float x1, float y1, float x2, float y2, float x3, float y3)
{
Point2D.Float control1 = new Point2D.Float(x1, y1);
Point2D.Float control2 = new Point2D.Float(x2, y2);
Point2D.Float end = new Point2D.Float(x3, y3);
segments.add(new Curve(currentPoint, control1, control2, end));
currentPoint = end;
}
void closePath()
{
closed = true;
currentPoint = start;
}
//
// Iterable<Segment> implementation
//
public Iterator<Segment> iterator()
{
return segments.iterator();
}
//
// Object override
//
@Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append(" {\n Start at: ")
.append(start.getX())
.append(", ")
.append(start.getY())
.append('\n');
for (Segment segment : segments)
builder.append(segment);
if (closed)
builder.append(" Closed\n");
builder.append(" }\n");
return builder.toString();
}
boolean closed = false;
final Point2D.Float start;
final List<Segment> segments = new ArrayList<Path.Segment>();
}
public class Rectangle extends SubPath
{
Rectangle(Point2D.Float p0, Point2D.Float p1, Point2D.Float p2, Point2D.Float p3)
{
super(p0);
lineTo((float)p1.getX(), (float)p1.getY());
lineTo((float)p2.getX(), (float)p2.getY());
lineTo((float)p3.getX(), (float)p3.getY());
closePath();
}
//
// Object override
//
@Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append(" {\n Rectangle\n Start at: ")
.append(start.getX())
.append(", ")
.append(start.getY())
.append('\n');
for (Segment segment : segments)
builder.append(segment);
if (closed)
builder.append(" Closed\n");
builder.append(" }\n");
return builder.toString();
}
}
public int getWindingRule()
{
return windingRule;
}
void complete(int windingRule)
{
finishSubPath();
this.windingRule = windingRule;
}
void appendRectangle(Point2D.Float p0, Point2D.Float p1, Point2D.Float p2, Point2D.Float p3) throws IOException
{
finishSubPath();
currentSubPath = new Rectangle(p0, p1, p2, p3);
finishSubPath();
}
void moveTo(float x, float y) throws IOException
{
finishSubPath();
currentSubPath = new SubPath(new Point2D.Float(x, y));
}
void lineTo(float x, float y) throws IOException
{
currentSubPath.lineTo(x, y);
}
void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) throws IOException
{
currentSubPath.curveTo(x1, y1, x2, y2, x3, y3);
}
Point2D.Float getCurrentPoint() throws IOException
{
return currentPoint;
}
void closePath() throws IOException
{
currentSubPath.closePath();
finishSubPath();
}
void finishSubPath()
{
if (currentSubPath != null)
{
subPaths.add(currentSubPath);
currentSubPath = null;
}
}
//
// Iterable<Path.SubPath> implementation
//
public Iterator<SubPath> iterator()
{
return subPaths.iterator();
}
//
// Object override
//
@Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append("{\n Winding: ")
.append(windingRule)
.append('\n');
for (SubPath subPath : subPaths)
builder.append(subPath);
builder.append("}\n");
return builder.toString();
}
Point2D.Float currentPoint = null;
SubPath currentSubPath = null;
int windingRule = -1;
final List<SubPath> subPaths = new ArrayList<Path.SubPath>();
}
A ClipPathFinder
osztály a következőképpen használatos:
PDDocument document = PDDocument.load(PDFRESOURCE, null);
PDPage page = document.getPage(PAGENUMBER);
ClipPathFinder finder = new ClipPathFinder(page);
finder.findClipPaths();
for (Path path : finder)
{
System.out.println(path);
}
document.close();
05.02.2015
PDFGraphicsStreamEngine
(amelyből aClipPathFinder
származik) a PDFBox 1.8.8PageDrawer
alap egy általánosabb mellékága. Valószínűleg módosítani lehet aPageDrawer class to serve the same purpose as
PDFGraphicsStreamEngine. Else one has to do more copy&paste and create one's own graphics stream engine based on the 1.8.8
PDFStreamEngine. To cut a long story short: It is possible but you have to find/create a replacement for the
PDFGraphicsStreamEngine alaposztályát. 05.02.2015PDFGraphicsStreamEngine
-t és az összesGraphicsOperatorProcessor
osztályát, ha szükségem lenne a funkcionalitásra az 1.8.8-ban. 05.02.2015n
operátorral végződik. Ez helyes? 20.06.2016paths.add(currentPath);
-t az elérési úthoz. festési módszereket éssegments.add(new Line(currentPoint,start));
a SubPath.closePath metódusra. Kipróbáltam ezt az Adobe PDF specifikációval (adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/), 6. oldal, és működött. 21.06.2016Path
ésClipPathFinder
osztályait is az eredmény reprodukálásához. És kérlek, tedd ezt is önálló kérdéssé, nehéz a kódot csak megjegyzésekben megfelelően megvitatni. 21.06.2016