How do I find which image field in PDF has image inserted and which one has no images attached using PDFbox 1.8.11? - itext

I have a PDF that has image fields inside it. I am not using a PDPushButton with javascript to attach pictures because if I do that the button's top layer gets replaced with the picture that I am attaching which is not what I want. So I am explicitly using a ImageField that is available in Adobe LiveCycle Designer. I am able to extract the files attached on it using PDFBox but I am not able to find any way of seeing which image fields have files attached to them and which ones do not. For example if I have the following code here:
ImageField[1], ImageField[2], ImageField[3]
I want to see something like
ImageField[1]: null,
ImageField[2]: true,
ImageField[3]: trueenter code here
etc assuming ImageField[2] and ImageField[3] has images attached to them.
Below is the code that I was working on:
I have a constant:
Then I am looping through the whole set of image field names and see which field is a instance of PDXObjectImage and then if it is a PDXObjectImage then I check if that object.getRGBImage().getHeight() > 0 assuming that only files uploaded have a height > 1 which means a file has been attached.
private static String[] IMAGE_FIELD_ROW = {"ImageField1[0]","ImageField2[0]",....} => 100 rows of string values such as "ImageField3[0]", "ImageField4[0]", ...etc.
for(int i = 0; i<IMAGE_FIELD_ROW.length; i++)
{
if(field.getPartialName().equals(IMAGE_FIELD_ROW[i]))
{
Map<String, PDAppearanceStream> stateAppearances = field.getWidget().getAppearance().getNormalAppearance();
for (Map.Entry<String, PDAppearanceStream> entry: stateAppearances.entrySet())
{
PDAppearanceStream appearance = entry.getValue();
PDResources resources = appearance.getResources();
if (resources == null)
return;
Map<String, PDXObject> xObjects = resources.getXObjects();
if (xObjects == null)
return;
for (Map.Entry<String, PDXObject> entryNew : xObjects.entrySet())
{
PDXObject xObject = entryNew.getValue();
System.out.println("printing out the xobject name: "+ entryNew.getKey());
if (xObject instanceof PDXObjectForm)
{
PDXObjectForm form = (PDXObjectForm)xObject;
PDResources resources2 = form.getResources();
if (resources2 == null)
return;
Map<String, PDXObject> xObjects2 = resources2.getXObjects();
if (xObjects2 == null)
{
return;
}
for (Map.Entry<String, PDXObject> entry2 : xObjects2.entrySet())
{
PDXObject xObject2 = entry2.getValue();
if (xObject2 instanceof PDXObjectForm)
{
continue;
}
else if (xObject2 instanceof PDXObjectImage)
{
PDXObjectImage ig = (PDXObjectImage)xObject2;
if(ig.getRGBImage().getHeight() > 0)
{
images.put(field.getPartialName(), "true");
}
else
{
images.put(field.getPartialName(), null);
}
//imageIds.add(imageId);
}
else
{
continue;
}
}
}
}
}
}
}
Images is a map variable: Mapimages.
Also my code file is large and so I didn't want to overwhelm anybody by pasting the whole file. Below is the dropbox link for the sample PDF file that I am using:
https://www.dropbox.com/s/g2wqm8ipsp8t8l5/GSA%20500%20PDF_v4.pdf?dl=0

Your PDF is a hybrid AcroForm/XFA document; where the XFA part uses fields with an imageEdit user interface, the AcroForm part uses pushbutton fields.
Thus, it allows you two ways to check whether an image field is set: Either you look at the AcroForm buttons and inspect their appearances for images, or you retrieve the XFA XML and inspect that.
Checking the XFA XML
Initially I did overlook the PDFBox version in the question title and implemented this for PDFBox 2.0.x. As it turns out, though, the identical code can be used for PDFBox 1.8.11, merely some additional exceptions may be thrown and, therefore, must be considered.
The latter option, inspecting the XFA XML, actually is a bit easier for the document at hand. Simply search the XML for an element with the name in question and check its contents. As an additional sanity check one can verify the content type attribute of the element:
boolean isFieldFilledXfa(Document xfaDom, String fieldName) {
NodeList fieldElements = xfaDom.getElementsByTagName(fieldName);
for (int i = 0; i < fieldElements.getLength(); i++) {
Node node = fieldElements.item(i);
if (node instanceof Element) {
Element element = (Element) node;
if (element.getAttribute("xfa:contentType").startsWith("image/")) {
return element.getTextContent().length() > 0;
}
}
}
return false;
}
(CheckImageFieldFilled helper method)
With it you can check your document:
PDDocument document = PDDocument.load(SOURCE);
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
Document xfaDom = acroForm.getXFA().getDocument();
System.out.println("Filled image fields from ImageField1..ImageField105:");
for (int i=1; i < 106; i++) {
if (isFieldFilledXfa(xfaDom, "ImageField" + i)) {
System.out.printf("* ImageField%d\n", i);
}
}
(CheckImageFieldFilled test method testCheckXfaGsa500Pdf_v4)
The output:
Filled image fields from ImageField1..ImageField105:
* ImageField1
* ImageField3
* ImageField6
Checking the AcroForm Appearances
The implementation here only works as is for PDFBox 2.0.x. The structure of the content stream parser classes has been considerably overhauled in 2.0.0, making a back-port of this code to 1.8.x a bit tedious.
To check whether the push button appearance actually shows an image (and not only has an image in its resources), one can use a simple PDFGraphicsStreamEngine subclass like this:
public class WidgetImageChecker extends PDFGraphicsStreamEngine
{
public WidgetImageChecker(PDAnnotationWidget widget) {
super(widget.getPage());
this.widget = widget;
}
public boolean hasImages() throws IOException {
count = 0;
PDAppearanceStream normalAppearance = widget.getNormalAppearanceStream();
processChildStream(normalAppearance, widget.getPage());
return count != 0;
}
#Override
public void drawImage(PDImage pdImage) throws IOException {
count++;
}
#Override
public void appendRectangle(Point2D p0, Point2D p1, Point2D p2, Point2D p3) throws IOException { }
#Override
public void clip(int windingRule) throws IOException { }
#Override
public void moveTo(float x, float y) throws IOException { }
#Override
public void lineTo(float x, float y) throws IOException { }
#Override
public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) throws IOException { }
#Override
public Point2D getCurrentPoint() throws IOException { return null; }
#Override
public void closePath() throws IOException { }
#Override
public void endPath() throws IOException { }
#Override
public void strokePath() throws IOException { }
#Override
public void fillPath(int windingRule) throws IOException { }
#Override
public void fillAndStrokePath(int windingRule) throws IOException { }
#Override
public void shadingFill(COSName shadingName) throws IOException { }
final PDAnnotationWidget widget;
int count = 0;
}
(CheckImageFieldFilled helper class)
With it you can create a check method like this:
boolean isFieldFilledAcroForm(PDAcroForm acroForm, String fieldName) throws IOException {
for (PDField field : acroForm.getFieldTree()) {
if (field instanceof PDPushButton && fieldName.equals(field.getPartialName())) {
for (final PDAnnotationWidget widget : field.getWidgets()) {
WidgetImageChecker checker = new WidgetImageChecker(widget);
if (checker.hasImages())
return true;
}
}
}
return false;
}
(CheckImageFieldFilled helper method)
and use it like this:
PDDocument document = PDDocument.load(SOURCE);
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
System.out.println("Filled image fields (AcroForm) from ImageField1..ImageField105:");
for (int i=1; i < 106; i++) {
if (isFieldFilledAcroForm(acroForm, "ImageField" + i + "[0]")) {
System.out.printf("* ImageField%d\n", i);
}
}
(CheckImageFieldFilled test testCheckAcroFormGsa500Pdf_v4)
The output, just like above:
Filled image fields (AcroForm) from ImageField1..ImageField105:
* ImageField1
* ImageField3
* ImageField6

Related

GWT image get from server

when I implemented as given here but for not working
my implementation is
ServerSide:
File f = fileFromDatabase // from database the fileName is india.png
DataInputStream din = new DataInputStream(new FileInputStream(f));
din.readFully(data);
din.close();
String base64 = Base64Utils.toBase64(data);
String[] s = filename.split("\\.");
base64 = "data:" + "india/png" + ";base64," + base64;
or
base64 = "data:image/png;base64," + base64;
return base64;
clientSide:
imageService.getImageData(new AsyncCallback() {
#Override
public void onSuccess(String imageData) {
Image image = new Image(imageData);
Canvas.addChild(image);
//this Canvas class addItem into com.smartgwt.client.widgets.Window
}
#Override
public void onFailure(Throwable caught) {
}
}
client side imageData stirng is <image class="gwt-Image src=sume big string starts with "data:image/png;base64,someSting......>"
eventhough client side could not see the image.
Please clear my doubt
Thanks in advance
When you get the base64 string from the server you need to add a load handler on the image in the DOM. The way I've done it is to attach it to the DOM and hide it as in the below code.
/** get events preview from server, attach to the DOM and store in 'preview'
* #param handler option to pass a custom load handler
*/
private void get_preview(LoadHandler handler) {
final LoadHandler load_handler = handler;
server.getPreviewImage(user, data, new AsyncCallback<String>() {
#Override
public void onFailure(Throwable caught) {
log.info("getPreviewImage: " + error);
}
#Override
public void onSuccess(String result) {
preview = null;
if (result != null) {
ImageElement ie = doc.createImageElement();
preview = Image.wrap(ie);
preview.setVisible(false);
doc.getElementById("imagedummy").removeAllChildren();
doc.getElementById("imagedummy").appendChild(preview.getElement());
// add load handler to DOM image before being able to use
if (load_handler == null) {
preview.addLoadHandler(new LoadHandler() {
#Override
public void onLoad(LoadEvent event) {
display_preview();
}
});
} else {
preview.addLoadHandler(load_handler);
}
preview.setUrl(result);
}
}
});
}
/** Displays the preview on the canvas.
* Resizes canvas if necessary and sets zoom
*/
private void display_preview() {
EventSize size = data.getEventSize();
canvas.canvas.setCoordinateSpaceWidth(size.width);
canvas.canvas.setCoordinateSpaceHeight(size.height);
float zoom = Float.parseFloat(preview_zoom.getSelectedValue());
canvas.canvas.setPixelSize((int)(size.width * zoom), (int)(size.height * zoom));
if (preview != null) {
ImageElement elem = ImageElement.as(preview.getElement());
canvas.canvas.getContext2d().drawImage(elem, 0, 0);
}
}

Manipulate paths, color etc. in iText

I need to analyze path data of PDF files and manipulate content with iText 7. Manipulations include deletion/replacemant and coloring.
I can analyze the graphics alright with something like the following code:
public class ContentParsing {
public static void main(String[] args) throws IOException {
new ContentParsing().inspectPdf("testdata/test.pdf");
}
public void inspectPdf(String path) throws IOException {
File file = new File(path);
PdfDocument pdf = new PdfDocument(new PdfReader(file.getAbsolutePath()));
PdfDocumentContentParser parser = new PdfDocumentContentParser(pdf);
for (int i=1; i<=pdf.getNumberOfPages(); i++) {
parser.processContent(i, new PathEventListener());
}
pdf.close();
}
}
public class PathEventListener implements IEventListener {
public void eventOccurred(IEventData eventData, EventType eventType) {
PathRenderInfo pathRenderInfo = (PathRenderInfo) eventData;
for ( Subpath subpath : pathRenderInfo.getPath().getSubpaths() ) {
for ( IShape segment : subpath.getSegments() ) {
// Here goes some path analysis code
System.out.println(segment.getBasePoints());
}
}
}
public Set<EventType> getSupportedEvents() {
Set<EventType> supportedEvents = new HashSet<EventType>();
supportedEvents.add(EventType.RENDER_PATH);
return supportedEvents;
}
}
Now, what's the way to go with manipulating things and writing them back to the PDF? Do I have to construct an entirely new PDF document and copy everything over (in manipulated form), or can I somehow manipulate the read PDF data directly?
Now, what's the way to go with manipulating things and writing them back to the PDF? Do I have to construct an entirely new PDF document and copy everything over (in manipulated form), or can I somehow manipulate the read PDF data directly?
In essence you are looking for a class which is not merely parsing a PDF content stream and signaling the instructions in it like the PdfCanvasProcessor (the PdfDocumentContentParser you use is merely a very thin wrapper for PdfCanvasProcessor) but which also creates the content stream anew with the instructions you forward back to it.
A generic content stream editor class
For iText 5.5.x a proof-of-concept for such a content stream editor class can be found in this answer (the Java version is a bit further down in the answer text).
This is a port of that proof-of-concept to iText 7:
public class PdfCanvasEditor extends PdfCanvasProcessor
{
/**
* This method edits the immediate contents of a page, i.e. its content stream.
* It explicitly does not descent into form xobjects, patterns, or annotations.
*/
public void editPage(PdfDocument pdfDocument, int pageNumber) throws IOException
{
if ((pdfDocument.getReader() == null) || (pdfDocument.getWriter() == null))
{
throw new PdfException("PdfDocument must be opened in stamping mode.");
}
PdfPage page = pdfDocument.getPage(pageNumber);
PdfResources pdfResources = page.getResources();
PdfCanvas pdfCanvas = new PdfCanvas(new PdfStream(), pdfResources, pdfDocument);
editContent(page.getContentBytes(), pdfResources, pdfCanvas);
page.put(PdfName.Contents, pdfCanvas.getContentStream());
}
/**
* This method processes the content bytes and outputs to the given canvas.
* It explicitly does not descent into form xobjects, patterns, or annotations.
*/
public void editContent(byte[] contentBytes, PdfResources resources, PdfCanvas canvas)
{
this.canvas = canvas;
processContent(contentBytes, resources);
this.canvas = null;
}
/**
* <p>
* This method writes content stream operations to the target canvas. The default
* implementation writes them as they come, so it essentially generates identical
* copies of the original instructions the {#link ContentOperatorWrapper} instances
* forward to it.
* </p>
* <p>
* Override this method to achieve some fancy editing effect.
* </p>
*/
protected void write(PdfCanvasProcessor processor, PdfLiteral operator, List<PdfObject> operands)
{
PdfOutputStream pdfOutputStream = canvas.getContentStream().getOutputStream();
int index = 0;
for (PdfObject object : operands)
{
pdfOutputStream.write(object);
if (operands.size() > ++index)
pdfOutputStream.writeSpace();
else
pdfOutputStream.writeNewLine();
}
}
//
// constructor giving the parent a dummy listener to talk to
//
public PdfCanvasEditor()
{
super(new DummyEventListener());
}
//
// Overrides of PdfContentStreamProcessor methods
//
#Override
public IContentOperator registerContentOperator(String operatorString, IContentOperator operator)
{
ContentOperatorWrapper wrapper = new ContentOperatorWrapper();
wrapper.setOriginalOperator(operator);
IContentOperator formerOperator = super.registerContentOperator(operatorString, wrapper);
return formerOperator instanceof ContentOperatorWrapper ? ((ContentOperatorWrapper)formerOperator).getOriginalOperator() : formerOperator;
}
//
// members holding the output canvas and the resources
//
protected PdfCanvas canvas = null;
//
// A content operator class to wrap all content operators to forward the invocation to the editor
//
class ContentOperatorWrapper implements IContentOperator
{
public IContentOperator getOriginalOperator()
{
return originalOperator;
}
public void setOriginalOperator(IContentOperator originalOperator)
{
this.originalOperator = originalOperator;
}
#Override
public void invoke(PdfCanvasProcessor processor, PdfLiteral operator, List<PdfObject> operands)
{
if (originalOperator != null && !"Do".equals(operator.toString()))
{
originalOperator.invoke(processor, operator, operands);
}
write(processor, operator, operands);
}
private IContentOperator originalOperator = null;
}
//
// A dummy event listener to give to the underlying canvas processor to feed events to
//
static class DummyEventListener implements IEventListener
{
#Override
public void eventOccurred(IEventData data, EventType type)
{ }
#Override
public Set<EventType> getSupportedEvents()
{
return null;
}
}
}
(PdfCanvasEditor.java)
The explanations from the iText 5 answer still apply, the parsing framework has not changed much from iText 5.5.x to iText 7.0.x.
Usage examples
Unfortunately you wrote in very vague terms about how exactly you want to change the contents. Thus I simply ported some iText 5 samples which made use of the original iText 5 content stream editor class:
Watermark removal
These are ports of the use cases in this answer.
testRemoveBoldMTTextDocument
This example drops all text written in a font the name of which ends with "BoldMT":
try ( InputStream resource = getClass().getResourceAsStream("document.pdf");
PdfReader pdfReader = new PdfReader(resource);
OutputStream result = new FileOutputStream(new File(RESULT_FOLDER, "document-noBoldMTText.pdf"));
PdfWriter pdfWriter = new PdfWriter(result);
PdfDocument pdfDocument = new PdfDocument(pdfReader, pdfWriter) )
{
PdfCanvasEditor editor = new PdfCanvasEditor()
{
#Override
protected void write(PdfCanvasProcessor processor, PdfLiteral operator, List<PdfObject> operands)
{
String operatorString = operator.toString();
if (TEXT_SHOWING_OPERATORS.contains(operatorString))
{
if (getGraphicsState().getFont().getFontProgram().getFontNames().getFontName().endsWith("BoldMT"))
return;
}
super.write(processor, operator, operands);
}
final List<String> TEXT_SHOWING_OPERATORS = Arrays.asList("Tj", "'", "\"", "TJ");
};
for (int i = 1; i <= pdfDocument.getNumberOfPages(); i++)
{
editor.editPage(pdfDocument, i);
}
}
(EditPageContent.java test method testRemoveBoldMTTextDocument)
testRemoveBigTextDocument
This example drops all text written with a large font size:
try ( InputStream resource = getClass().getResourceAsStream("document.pdf");
PdfReader pdfReader = new PdfReader(resource);
OutputStream result = new FileOutputStream(new File(RESULT_FOLDER, "document-noBigText.pdf"));
PdfWriter pdfWriter = new PdfWriter(result);
PdfDocument pdfDocument = new PdfDocument(pdfReader, pdfWriter) )
{
PdfCanvasEditor editor = new PdfCanvasEditor()
{
#Override
protected void write(PdfCanvasProcessor processor, PdfLiteral operator, List<PdfObject> operands)
{
String operatorString = operator.toString();
if (TEXT_SHOWING_OPERATORS.contains(operatorString))
{
if (getGraphicsState().getFontSize() > 100)
return;
}
super.write(processor, operator, operands);
}
final List<String> TEXT_SHOWING_OPERATORS = Arrays.asList("Tj", "'", "\"", "TJ");
};
for (int i = 1; i <= pdfDocument.getNumberOfPages(); i++)
{
editor.editPage(pdfDocument, i);
}
}
(EditPageContent.java test method testRemoveBigTextDocument)
Text color change
This is a port of the use case in this answer.
testChangeBlackTextToGreenDocument
This example changes the color of black text to green.
try ( InputStream resource = getClass().getResourceAsStream("document.pdf");
PdfReader pdfReader = new PdfReader(resource);
OutputStream result = new FileOutputStream(new File(RESULT_FOLDER, "document-blackTextToGreen.pdf"));
PdfWriter pdfWriter = new PdfWriter(result);
PdfDocument pdfDocument = new PdfDocument(pdfReader, pdfWriter) )
{
PdfCanvasEditor editor = new PdfCanvasEditor()
{
#Override
protected void write(PdfCanvasProcessor processor, PdfLiteral operator, List<PdfObject> operands)
{
String operatorString = operator.toString();
if (TEXT_SHOWING_OPERATORS.contains(operatorString))
{
if (currentlyReplacedBlack == null)
{
Color currentFillColor = getGraphicsState().getFillColor();
if (Color.BLACK.equals(currentFillColor))
{
currentlyReplacedBlack = currentFillColor;
super.write(processor, new PdfLiteral("rg"), Arrays.asList(new PdfNumber(0), new PdfNumber(1), new PdfNumber(0), new PdfLiteral("rg")));
}
}
}
else if (currentlyReplacedBlack != null)
{
if (currentlyReplacedBlack instanceof DeviceCmyk)
{
super.write(processor, new PdfLiteral("k"), Arrays.asList(new PdfNumber(0), new PdfNumber(0), new PdfNumber(0), new PdfNumber(1), new PdfLiteral("k")));
}
else if (currentlyReplacedBlack instanceof DeviceGray)
{
super.write(processor, new PdfLiteral("g"), Arrays.asList(new PdfNumber(0), new PdfLiteral("g")));
}
else
{
super.write(processor, new PdfLiteral("rg"), Arrays.asList(new PdfNumber(0), new PdfNumber(0), new PdfNumber(0), new PdfLiteral("rg")));
}
currentlyReplacedBlack = null;
}
super.write(processor, operator, operands);
}
Color currentlyReplacedBlack = null;
final List<String> TEXT_SHOWING_OPERATORS = Arrays.asList("Tj", "'", "\"", "TJ");
};
for (int i = 1; i <= pdfDocument.getNumberOfPages(); i++)
{
editor.editPage(pdfDocument, i);
}
}
(EditPageContent.java test method testChangeBlackTextToGreenDocument)

How to create sub documents for Itext PDF

I have been tasked with creating a work order application where the
output is PDF for printing from a browser. The output has to be one PDF
document containing one or more "work orders". Due to variable content length each work order might be more than one printed page. Each work order must have its own "page" numbering. I am using Spring MVC and Itext 5.
So imagine we have three work orders. The first will fit on one printed
page, the second requires two printed pages, and the third requires one printed page. We have a total of four printed pages.
How do make so that:
Work order one would have a page number of "1"
Work order two would have page numbers "1, 2"
Work order three would page number "1"
So basically the PDF document would be a container for multiple inner
documents that are independent from each other.
I have tried to reset the page numbers and use the footer for output but that does not seem to work. I am wondering if there some way to set a custom page counter that I can control.
public class PDFBuilder extends AbstractITextPdfView
{
#Override
protected void buildPdfDocument(Map<String, Object> model, Document doc,
PdfWriter writer, HttpServletRequest request, HttpServletResponse response)
throws Exception
{
#SuppressWarnings("unchecked") // TODO replace this with some generic goodness
List<WorkOrderDto> workOrders = (List<WorkOrderDto>) model.get("workOrders");
Integer pageCounter = 1;
// define table header cell
PdfPCell cell = new PdfPCell();
cell.setBackgroundColor(BaseColor.BLUE);
cell.setPadding(5);
// define font for table header row
Font font = FontFactory.getFont(FontFactory.HELVETICA);
font.setColor(BaseColor.WHITE);
writer.setPageEvent(new Footer(pageCounter));
for (WorkOrderDto dto : workOrders)
{
PdfPTable woHeader = new PdfPTable(4);
woHeader.setWidthPercentage(100.0f);
woHeader.setSpacingBefore(10);
if (dto.getNumber() != null)
{
woHeader.addCell("Number: " + dto.getNumber());
}
else
{
woHeader.addCell("Number: ");
}
if (dto.getOwnerNumber() != null)
{
woHeader.addCell("Owner: " + dto.getOwnerNumber());
}
else
{
woHeader.addCell("Owner: ");
}
if (dto.getTypeNumber() != null)
{
woHeader.addCell("Type: " + dto.getTypeNumber());
}
else
{
woHeader.addCell("Type: ");
}
if (dto.getScheduleDate() != null)
{
woHeader.addCell("Open Date: " + dto.getScheduleDate().toString());
}
else
{
woHeader.addCell("Open Date: ");
}
doc.add(woHeader);
PdfPTable servicesTable = new PdfPTable(4);
for (ServiceDto serviceDto : dto.getCurrentServices())
{
servicesTable.addCell("Number: " + serviceDto.getNumber());
servicesTable.addCell("Name: " + serviceDto.getName());
servicesTable.addCell("Description: " + serviceDto.getDescription());
servicesTable.addCell("Status: " + serviceDto.getStatus());
}
doc.add(servicesTable);
doc.newPage();
pageCounter++;
}
}
}
public class Footer implements PdfPageEvent
{
Font ffont = new Font(Font.FontFamily.UNDEFINED, 10, Font.ITALIC);
Integer pageCounter;
public Footer(Integer pageCounter)
{
this.pageCounter = pageCounter;
}
#Override
public void onOpenDocument(PdfWriter writer, Document document)
{
}
#Override
public void onStartPage(PdfWriter writer, Document document)
{
}
#Override
public void onEndPage(PdfWriter writer, Document document)
{
PdfContentByte cb = writer.getDirectContent();
Phrase footer = new Phrase(pageCounter + "", ffont);
ColumnText.showTextAligned(cb, Element.ALIGN_CENTER,
footer,
(document.right() - document.left()) / 2 + document.leftMargin(),
document.bottom() - 10, 0);
}
#Override
public void onCloseDocument(PdfWriter writer, Document document)
{
}
#Override
public void onParagraph(PdfWriter writer, Document document, float paragraphPosition)
{
}
#Override
public void onParagraphEnd(PdfWriter writer, Document document, float paragraphPosition)
{
}
#Override
public void onChapter(PdfWriter writer, Document document, float paragraphPosition, Paragraph title)
{
}
#Override
public void onChapterEnd(PdfWriter writer, Document document, float paragraphPosition)
{
}
#Override
public void onSection(PdfWriter writer, Document document, float paragraphPosition, int depth, Paragraph title)
{
}
#Override
public void onSectionEnd(PdfWriter writer, Document document, float paragraphPosition)
{
}
#Override
public void onGenericTag(PdfWriter writer, Document document, Rectangle rect, String text)
{
}
}
I came up with my own solution which turned out to be very simple. I created a class called PageCounter with some properties to represent various counters. I pass that to the footer and then manipulate the properties while building out the pages.
public class PageCounter
{
private Integer currentPage = 1;
private Integer totalPages = 1;
public Integer getCurrentPage()
{
return currentPage;
}
public void setCurrentPage(Integer currentPage)
{
this.currentPage = currentPage;
}
public Integer getTotalPages()
{
return totalPages;
}
public void setTotalPages(Integer totalPages)
{
this.totalPages = totalPages;
}
public void increment()
{
this.currentPage++;
}
public void reset()
{
this.currentPage = 1;
}
}
public class PDFBuilder extends AbstractITextPdfView
{
#Override
protected void buildPdfDocument(Map<String, Object> model, Document doc,
PdfWriter writer, HttpServletRequest request, HttpServletResponse response)
throws Exception
{
#SuppressWarnings("unchecked") // TODO replace this with some generic goodness
List<WorkOrderDto> workOrders = (List<WorkOrderDto>) model.get("workOrders");
PageCounter pageCounter = new PageCounter();
// define table header cell
PdfPCell cell = new PdfPCell();
cell.setBackgroundColor(BaseColor.BLUE);
cell.setPadding(5);
// define font for table header row
Font font = FontFactory.getFont(FontFactory.HELVETICA);
font.setColor(BaseColor.WHITE);
writer.setPageEvent(new Footer(pageCounter));
for (WorkOrderDto dto : workOrders)
{
PdfPTable woHeader = new PdfPTable(4);
woHeader.setWidthPercentage(100.0f);
woHeader.setSpacingBefore(10);
if (dto.getNumber() != null)
{
woHeader.addCell("Number: " + dto.getNumber());
}
else
{
woHeader.addCell("Number: ");
}
if (dto.getOwnerNumber() != null)
{
woHeader.addCell("Owner: " + dto.getOwnerNumber());
}
else
{
woHeader.addCell("Owner: ");
}
if (dto.getTypeNumber() != null)
{
woHeader.addCell("Type: " + dto.getTypeNumber());
}
else
{
woHeader.addCell("Type: ");
}
if (dto.getScheduleDate() != null)
{
woHeader.addCell("Open Date: " + dto.getScheduleDate().toString());
}
else
{
woHeader.addCell("Open Date: ");
}
doc.add(woHeader);
PdfPTable servicesTable = new PdfPTable(4);
for (ServiceDto serviceDto : dto.getCurrentServices())
{
servicesTable.addCell("Number: " + serviceDto.getNumber());
servicesTable.addCell("Name: " + serviceDto.getName());
servicesTable.addCell("Description: " + serviceDto.getDescription());
servicesTable.addCell("Status: " + serviceDto.getStatus());
}
doc.add(servicesTable);
doc.newPage();
pageCounter.reset();
}
public class Footer implements PdfPageEvent
{
Font ffont = new Font(Font.FontFamily.UNDEFINED, 10, Font.ITALIC);
PageCounter pageCounter;
public Footer(PageCounter pageCounter)
{
this.pageCounter = pageCounter;
}
#Override
public void onOpenDocument(PdfWriter writer, Document document)
{
}
#Override
public void onStartPage(PdfWriter writer, Document document)
{
}
#Override
public void onEndPage(PdfWriter writer, Document document)
{
PdfContentByte cb = writer.getDirectContent();
Phrase footer = new Phrase(pageCounter.getCurrentPage() + "", ffont);
pageCounter.increment();
ColumnText.showTextAligned(cb, Element.ALIGN_CENTER,
footer,
(document.right() - document.left()) / 2 + document.leftMargin(),
document.bottom() - 10, 0);
}
#Override
public void onCloseDocument(PdfWriter writer, Document document)
{
}
#Override
public void onParagraph(PdfWriter writer, Document document, float paragraphPosition)
{
}
#Override
public void onParagraphEnd(PdfWriter writer, Document document, float paragraphPosition)
{
}
#Override
public void onChapter(PdfWriter writer, Document document, float paragraphPosition, Paragraph title)
{
}
#Override
public void onChapterEnd(PdfWriter writer, Document document, float paragraphPosition)
{
}
#Override
public void onSection(PdfWriter writer, Document document, float paragraphPosition, int depth, Paragraph title)
{
}
#Override
public void onSectionEnd(PdfWriter writer, Document document, float paragraphPosition)
{
}
#Override
public void onGenericTag(PdfWriter writer, Document document, Rectangle rect, String text)
{
}
}

Refresh suggestion list on change - cn1 autocomplete

I've implemented a custom autocomplete text field in a cn1 app, but I've noticed it only loads the suggestions list once, after that any change in the text doesn't trigger a change in the list, and the getSuggestionModel() is never called again. How can I achieve this (in my mind, basic) functionality?
This is my autocomplete class:
public class ForumNamesAutocomplete extends AutoCompleteTextField {
List<String>suggestions = new LinkedList<String>();
List<Map<String,Object>> fData;
StateMachine mac;
int currentIndex;
String prevText;
public static final String KEY_FORUM_NAME = "name";
public static final String KEY_FORUM_ID = "id";
public static final String KEY_FORUM_DESC = "desc";
public ForumNamesAutocomplete(StateMachine sm){
super();
mac = sm;
if(sm.forumData != null){
fData = mac.forumData;
}
}
#Override
protected boolean filter(String text) {
if(text.equals(prevText)){
return false;
}
setSuggestionList(text);
fireDataChanged(DataChangedListener.CHANGED, text.length());
prevText = text;
return true;
}
#Override
public void fireDataChanged(int type, int index) {
super.fireDataChanged(type, index);
}
public void setSuggestionList(String s){
if(suggestions == null){
suggestions = new LinkedList<String>();
}else{
suggestions.clear();
}
LinkedList<String> descList = new LinkedList<String>();
for(int i = 0;i<fData.size();i++){
boolean used = false;
Map<String,Object> forumMap = fData.get(i);
if(((String)forumMap.get(KEY_FORUM_NAME)).indexOf(s) != -1){
suggestions.add((String)forumMap.get(KEY_FORUM_NAME));
used = true;
}
if(!used && ((String)forumMap.get(KEY_FORUM_DESC)).indexOf(s) != -1){
descList.add((String)forumMap.get(KEY_FORUM_NAME));
}
}
suggestions.addAll(descList);
}
#Override
protected ListModel<String> getSuggestionModel() {
return new DefaultListModel<String>(suggestions);
}
}
This used to be simpler and seems to be a bit problematic now as explained in this issues.
Technically what you need to do is return one model and then mutate said model/fire modified events so everything will refresh. This is non-trivial and might not work correctly for all use cases so ideally we should have a simpler API to do this as we move forward.
After additional debugging, I saw that the getSuggestionModel() method was being called only during initialization, and whatever the suggestion list (in suggestion object) was at that point, it remained so. Instead I needed to manipulate the underlying ListModel object:
public class ForumNamesAutocomplete extends AutoCompleteTextField {
ListModel<String>myModel = new ListModel<String>();
...
#Override
protected boolean filter(String text) {
if(text.length() > 1){
return false;
}
setSuggestionList(text);
return true;
}
private void setSuggestionList(String s){
if(myModel == null){
myModel = new ListModel<String>();
}else{
while(myModel.getSize() > 0)
myModel.removeItem(0);
}
for(int i = 0;i<fData.size();i++){
boolean used = false;
Map<String,Object> forumMap = fData.get(i);
if(((String)forumMap.get(KEY_FORUM_NAME)).indexOf(s) != -1){
myModel.addItem((String)forumMap.get(KEY_FORUM_NAME));
used = true;
}
if(!used && ((String)forumMap.get(KEY_FORUM_DESC)).indexOf(s) != -1){
myModel.addItem((String)forumMap.get(KEY_FORUM_NAME));
}
}
}
...
}

Drag and drop to other applications and OS?

I'm using JavaFX's Drag and Drop system in my application, and it has been working well so far.
Now I want to support drag and drop to outside applications, eg. dragging files from my application to the explorer. How would I achieve that?
I've achieved what you described by using:
Vector<File> files = new Vector<File>();
private ClipboardContent filesToCopyClipboard = new ClipboardContent();
...
final ObjectWithAReturnablePathField draggableObj = new ObjectWithAReturnablePathField();
...
draggableObj.setOnDragDetected(new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent me)
{
Dragboard db = draggableObj.startDragAndDrop(TransferMode.ANY);
try
{
File f = new File(new URI(draggableObj.getFilePath()));
files.add(f);
filesToCopyClipboard.putFiles(files);
}
catch (URISyntaxException e)
{
e.printStackTrace();
}
db.setContent(filesToCopyClipboard);
me.consume();
}
});
draggableObj.setOnDragDone(new EventHandler<DragEvent>()
{
#Override
public void handle(DragEvent me)
{
me.consume();
}
});
Which means:
It's possible to achieve file transference between JavaFX 2 and a native application by filling a ClipboardContent with a list using the TransferMode.ANY on the setOnDragDetected method of any Draggable Object (Any Node) which can return a Path for a file. In my case, I've created a class called Thumb extending ImageView and (among others things) I made a method called getFilePath() which returns the Path from the Image used to initialize the ImageView(). I'm sorry BTW for the poor example and the poor english, but I'm running out of time to give a more detailed answer as of now. I hope it helps. Cheers
Here is a sample source for an action listener on an ImageView image extraction to OS' explorer (With a custom process for jpg image to remove alpha-channel to display it correctly):
inputImageView.setOnDragDetected(new EventHandler <MouseEvent>() {
#Override
public void handle(MouseEvent event) {
// for paste as file, e.g. in Windows Explorer
try {
Clipboard clipboard Clipboard.getSystemClipboard();
Dragboard db = inputImageView.startDragAndDrop(TransferMode.ANY);
ClipboardContent content = new ClipboardContent();
Image sourceImage = inputImageView.getImage();
ImageInfo imageInfo = (ImageInfo) inputImageView.getUserData();
String name = FilenameUtils.getBaseName(imageInfo.getName());
String ext = FilenameUtils.getExtension(imageInfo.getName());
///Avoid get "prefix lenght too short" error when file name lenght <= 3
if (name.length() < 4){
name = name+Long.toHexString(Double.doubleToLongBits(Math.random()));;
}
File temp = File.createTempFile(name, "."+ext);
if (ext.contentEquals("jpg")|| ext.contentEquals("jpeg")){
BufferedImage image = SwingFXUtils.fromFXImage(sourceImage, null); // Get buffered image.
BufferedImage imageRGB = new BufferedImage(image.getWidth(),image.getHeight(),
BufferedImage.OPAQUE);
Graphics2D graphics = imageRGB.createGraphics();
graphics.drawImage(image, 0, 0, null);
ImageIO.write(imageRGB, ext, temp);
graphics.dispose();
ImageIO.write(imageRGB,
ext, temp);
}else{
ImageIO.write(SwingFXUtils.fromFXImage(sourceImage, null),
ext, temp);
}
content.putFiles(java.util.Collections.singletonList(temp));
db.setContent(content);
clipboard.setContent(content);
event.consume();
temp.deleteOnExit();
} catch (IOException ex) {
System.out.println(ex.getMessage());
}
}
});
With the help of use of an Object that is passed to the imageView's setUserData method, it helps me to retrieve database id and pic name:
public class ImageInfo {
private String imageInfo;
private int inputId;
#Override
public String toString() {
return imageInfo;
}
public ImageInfo(String imageInfo, int inputId) {
this.imageInfo = imageInfo;
this.inputId = inputId;
}
public String getName() {
return imageInfo;
}
public void setName(String imageInfo) {
this.imageInfo = imageInfo;
}
public int getIndex() {
return inputId;
}
public void setIndex(int areaindex) {
this.inputId = inputId;
}
}
I hope it will help somenone at an expected time :-)
Regards