Mixing page-level elements and column text - itext7

Trying to tap into iText7 potential, that is in there, but hidden from the developer. I want my text in columns to flow around images that will be placed on page-level (fixed position). Either I'm doing something wrong, or there is a bug in the code. Here is what I have so far:
open Document for writing
set document renderer to custom ColumnDocumentRenderer:
Class<RootRenderer> cls = RootRenderer.class;
final Field f = cls.getDeclaredField("floatRendererAreas");
f.setAccessible(true);
ColumnDocumentRenderer cdr = new ColumnDocumentRenderer(document, new Rectangle[]{
new Rectangle(36f, 36f, 243.5f, 770f),
new Rectangle(315.5f, 36f, 243.5f, 770f)
}) {
#Override
protected LayoutArea updateCurrentArea(LayoutResult overflowResult) {
LayoutArea lr = super.updateCurrentArea(overflowResult);
try {
List<Rectangle> list = (List<Rectangle>) f.get(this);
list.add(new Rectangle(130f, 230f, 350f, 200f));
} catch (Exception ex) {
throw new RuntimeException(ex);
}
return lr;
}
};
document.setRenderer(cdr);
This custom renderer adds the rectangle in which I will later place an image to floatRendererAreas field, so that the renderers will know which area to avoid.
add my content:
Div div = new Div();
div.setBackgroundColor(ColorConstants.BLUE);
div.setMargins(0f, 0f, 0f, 0f);
div.setPaddings(0f, 0f, 0f, 0f);
div.setFixedPosition(130f, 230f, 350f);
div.setHeight(200f);
document.add(div);
for (int i = 0; i < 6; i++) {
Paragraph p = new Paragraph();
p.add("Lorem ipsum...");
document.add(p);
}
unfortunately I get this:
edit: the layouting code is aware of the forbidden area, it just detects it too late, so up to one line is rendered in a wrong way. With y set to 260:
Probably it looks that the top of the text line is not affected, whereas it should check whether bottom of the text line is not affected as well.
What am I doing wrong? Is there another method to do it? Why is this not part of public/protected API? It could be extremely useful when creating documents.

Related

How to add multiple image into EditText using spannable or any other way?

I am creating a note taking app, I have done text section and its working properly. Also database working properly. But I want to add image in the EditText. When user add an image, it will place at the EditText cursor position and this task will happen for multiple image. And save the EditText entities (text and image) in SQL Lite database.
Please help someone to do this job. Thank you.
I have tried this job in onActivityResult , but image are not showing
if(requestCode==1 && resultCode==RESULT_OK && data!=null) {
Uri imageUri= data.getData();
try {
InputStream inputStream= getContentResolver().openInputStream(imageUri);
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
final Drawable drawable = new BitmapDrawable(getResources(), bitmap);
final ImageSpan imageSpan = new ImageSpan(drawable,ImageSpan.ALIGN_BOTTOM);
Spannable span = new SpannableStringBuilder(editText.getText().toString()+"\n");
span.setSpan(imageSpan, 0, 0, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
editText.append(span, 0, ("\n").length());
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
You are replacing zero characters when you do the following:
span.setSpan(imageSpan, 0, 0, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
You will need to replace at least one character. Since you are inserting an ImageSpan, you will need to add at least one character to replace.
ImageSpan imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_BOTTOM);
Editable editable = editText.getText();
// Insert a single space to replace.
editable = editable.insert(0, " ");
editable.setSpan(imageSpan, 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
editText.setText(editable);

iText PDF using .getSplitRenderer for Table renderer

In iText PDF 7, I am using the .layout method of the Table renderer to determine whether a table will break across a page.
However, when I add the .getSplitRenderer (returned from the layout result object) as a child of the Documents's renderer, I get this error: "java.lang.IndexOutOfBoundsException".
I'm using iText PDF version 7.1.7 in its Java incarnation. The last three entries in the stacktrace are:
java.util.ArrayList$SubList.rangeCheck(ArrayList.java:1225)
java.util.ArrayList$SubList.get(ArrayList.java:1042)
com.itextpdf.layout.renderer.TableBorders.processAllBordersAndEmptyRows(TableBorders.java:139)
Here is a bare-bones version of the code that triggers the error:
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
PdfWriter pdfWriter = new PdfWriter(outputStream);
PdfDoc pdfDoc = new PdfDocument(pdfWriter);
PageSize pageSize = new PageSize(612, 792);
Document doc = new Document(pdfDoc, pageSize);
Table table = new Table([50, 50, 50]);
for (int i = 0; i < 1000; i++) {
for (int j = 0; j < 3; j++) {
Cell cell = new Cell();
cell.setHeight(100);
table.addCell(cell);
}
}
LayoutContext context = new LayoutContext(doc.getRenderer().getCurrentArea().clone());
TableRenderer tableRenderer = (TableRenderer)table.createRendererSubTree();
LayoutResult result = tableRenderer.setParent(doc.getRenderer()).layout(context);
if (result.getStatus() == result.PARTIAL) {
tableRenderer = (TableRenderer) result.getSplitRenderer();
doc.getRenderer().addChild(tableRenderer); // this is where the error occurs
}
When you add a child to the DocumentRenderer it will layout and draw it automatically. It is not possible to layout a renderer several times in most cases (although what can be improved here is the exception type and message).
If you want to draw the part that fits immediately you can use the following line:
tableRenderer.draw(new DrawContext(pdfDocument, new PdfCanvas(pdfDocument.getPage(doc.getRenderer().getCurrentArea().getPageNumber()))));
Complete if expression:
if (result.getStatus() == LayoutResult.PARTIAL) {
tableRenderer = (TableRenderer) result.getSplitRenderer();
tableRenderer.draw(new DrawContext(pdfDocument, new PdfCanvas(pdfDocument.getPage(doc.getRenderer().getCurrentArea().getPageNumber()))));
}
It might have some drawbacks in complex cases though, so if you are dealing with complex layout or tagged documents I would recommend using binary search to determine the amount of content that still fits and add that content as an element to Document instance still.
An approach that is between those two is adding the table completely and then removing the extra pages from PdfDocument. In this case keep in mind that you will have to recreate the DocumentRenderer because it does not keep track of low level events like page removal from PdfDocument.

How to add multiple Textfields in single or multiple pages in a Loop

I am Using Itext 5 maven and I want to add multiple textfields in multiple pdf pages. like page 1 need 3 fields, page 2 need 4 fields etc.
I have write the below code
public byte[] setupDocument(EditPdfDTO editPdfDTOList, MultipartFile attachment)
{
WritePDF obj = new WritePDF();
Document document = null;
PdfWriter writer = null;
PdfImportedPage page = null;
PdfReader reader = null;
try
{
// Create output PDF
document = new Document(PageSize.A4);
document.setMargins(0, 0, 0, 0);
writer = PdfWriter.getInstance(document,
new FileOutputStream("D:/test.pdf"));
document.open();
PdfContentByte cb = writer.getDirectContent();
// Load existing PDF
reader = new PdfReader(attachment.getBytes());
int totalPages = reader.getNumberOfPages();
for (int i = 0; i < totalPages; i++)
{
page = writer.getImportedPage(reader, i + 1);
document.newPage();
cb.addTemplate(page, 0, 0);
for (int j = 0; j < editPdfDTOList.getPdf().size(); j++)
{
if (i + 1 == editPdfDTOList.getPdf().get(j).getPageNo())
{
BaseFont baseFont = null;
try
{
baseFont = BaseFont.createFont();
}
catch (DocumentException | IOException e1)
{
e1.printStackTrace();
}
int a, b;
a = editPdfDTOList.getPdf().get(j).getxCoordinate();
b = editPdfDTOList.getPdf().get(j).getyCoordinate();
String str = editPdfDTOList.getPdf().get(j).getTextContent();
Rectangle linkLocation =
new Rectangle(a, b + baseFont.getDescentPoint(str, 10),
a + 10 + baseFont.getWidthPoint(str, 10),
b + baseFont.getAscentPoint(str, 10) + 10);
TextField field =
new TextField(writer, linkLocation, "user1" + j+UUID.randomUUID());
field.setFontSize(10);
field.setOptions(TextField.MULTILINE | TextField.READ_ONLY);
field.setTextColor(BaseColor.RED);
field.setText(str);
field.setBorderWidth(1);
cb = writer.getDirectContent();
try
{
cb.addAnnotation(field.getTextField(),false);
}
catch (IOException | DocumentException e)
{
e.printStackTrace();
}
}
}
}
}
catch (DocumentException | IOException e)
{
e.printStackTrace();
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
document.close();
}
return null;
}
this code is able to add only one Textfield on every expected but not to add 2 or many textfields in a single page.
there is no issue of multiple try--catch block.
The appropriate classes to use
First of, you say you "want to add multiple textfields in multiple pdf pages". When implementing tasks like this, i.e. tasks that take a single document and want to somehow manipulate it while keeping it structurally more or less as before, one should usually work with a PdfReader/PdfStamper couple. This allows you to concentrate on the manipulation and provides a copy of the original PDF with all its properties to work on.
Adding multiple fields to a page of an existing PDF
Adding multiple fields to a single existing page is trivial, e.g.:
PdfReader pdfReader = new PdfReader(resource);
PdfStamper pdfStamper = new PdfStamper(pdfReader, output);
TextField field1 = new TextField(pdfStamper.getWriter(),
new Rectangle(100, 800, 200, 820), "Field1");
field1.setBorderColor(BaseColor.CYAN);
field1.setBorderStyle(PdfBorderDictionary.STYLE_DASHED);
field1.setBorderWidth(BaseField.BORDER_WIDTH_MEDIUM);
field1.setText("Field 1");
pdfStamper.addAnnotation(field1.getTextField(), 1);
TextField field2 = new TextField(pdfStamper.getWriter(),
new Rectangle(300, 800, 400, 820), "Field2");
field2.setBorderColor(BaseColor.RED);
field2.setBorderStyle(PdfBorderDictionary.STYLE_INSET);
field2.setBorderWidth(BaseField.BORDER_WIDTH_THIN);
field2.setText("Field 2");
pdfStamper.addAnnotation(field2.getTextField(), 1);
pdfStamper.close();
(AddField test testAddMultipleFields)
Applied to my example document
the code generates
Thus, there is no conceptual problem adding multiple text fields to the same document page, it works in a very natural manner.
In your case I would switch to using a PdfReader/PdfStamper couple. If some issue still remain, I would inspect your data. Probably they simply contain only a single field dataset per page. Or two textfields have the same coordinates and, therefore, look like one. Or some text fields have off-screen coordinates. Or... Or... Or...
The original answer
Originally the code in the question looked differently. This original answer focused on issues of that code.
You claim your code
is able to add only one Textfield on every expected but not to add 2 or many textfields in a single page
I doubt that because
you have two distinct objects writing to the same file "D:/TemplateFilePDf/" + attachment.getOriginalFilename() concurrently, the PdfWriter writer and the PdfStamper stamper. If you get something sensible as a result of your code, then only by pure luck; and
additionally stamper is instantiated for a null instance of PdfReader. This actually will cause a NullPointerException in the constructor which will keep your textfield adding code from being executed at all.
Thus, either the code you shared is considerably different from the code you run or your test runs actually all throw that NullPointerException and you probably find the outputs of a former, less broken version of your code which happens to have added only a single text field.
After fixing those two issues, some questions still remain (e.g. what is the intention of that cb.fill()? That instruction is only allowed directly after a path definition, the path whose inner area to fill, but I don't see you defining any path).
Furthermore, you access your editPdfDTOList for a lot of relevant values but we don't know those values. Thus, we cannot run your code to try and reproduce the issue. Probably you create only a single textfield because that object contains only values for a single textfield...

Add Image dynamically in runtime - Unity 5

In my unity based Android game I wish to add the image dynamically based on the number of questions in each level. The image is shown for reference. Each correct answer will be marked in green and the wrong one in red. I am new to unity and trying hard to find steps to achieve this.
Any help with an example for this requirement will be a great help.
I once wrote a script for dynamically creating buttons based on each level. What I did was creating the first button on the scene and adding the other buttons based on the first one. Below is the shell of my code:
// tutorialButton and levelButtons are public variables which can be set from Inspector
RectTransform rect = tutorialButton.GetComponent<RectTransform> ();
for (int i = 1; i < levelSize; i++) {
// Instantiate the button dynamically
GameObject newButton = GameObject.Instantiate (tutorialButton);
// Set the parent of the new button (In my case, the parent of tutorialButton)
newButton.transform.SetParent (levelButtons.transform);
//Set the scale to be the same as the tutorialButton
newButton.transform.localScale = tutorialButton.transform.localScale;
//Set the position to the right of the tutorialButton
Vector3 position = tutorialButton.transform.localPosition;
position.x += rect.rect.width*i;
newButton.transform.localPosition = position;
}
I am not exactly sure if this is the right approach as it may or may not give unexpected results depending on different screen sizes and your canvas, but hopefully it gives you an idea about dynamically creating objects.
I'm not sure if this helps, but if you have all the images in the scene under a canvas, with this you just need to drag the canvas on the script and use
//level-1 is to keep the array notation
FindObjectOfType<NameOfScript>.ChangeColor(level-1,Color.green);
or you can do also
//level-1 is to keep the array notation
FindObjectOfType<NameOfScript>.RevertColor(level - 1);
This is the script:
//Keep it private but you still see it in inspector
//#Encapsulation :)
[SerializeField]
private Canvas _canvas;
private Image[] _images;
//keep the original colors in case you want to change back
private Color[] _origColors;
void Start () {
_images = GetComponentsInChildren<Image>();
_origColors = new Color[_images.Length];
for (int i = 0; i < _images.Length; i++)
{
_origColors[i] = _images[i].color;
}
}
//Reverts the color of the image back to the original
public void RevertToOriginal(int imageIndex)
{
_images[imageIndex].color = _origColors[imageIndex];
}
//Change to color to the coresponding index, starts from 0
public void ChangeColor(int imageIndex, Color color)
{
_images[imageIndex].color = color;
}
P.S If you want it visible only at the end you can make a method where you enable = (true or false) for the canvas. So you keep it false till the end of the level and you make it true when you want to show, while after every answer you call the ChangeColor depending on the result.
To make it easier you can use:
NameOfScript variableName = FindObjectOfType<NameOfScript>();
and after that you just call
variableName.ChangeColor(level - 1, Color.green);
Also it does not matter where you put the script. I would make some kind of manager(empty GameObject) in the scene and put it there.

Changing alpha of Text's CanvasGroup in OnTriggerEnter Unity

I'm currently working on a game that pays hommage to Marble Blast Gold/Ultra.
At this point I have text that is positioned with the marble. The text is a child of a canvas and I have a canvas group added to the text. I initially set the alpha of the canvas group to 0 so that you can't see the text.
What I'm trying to do is have it so that when you pick up a power up the text reappears by changing the canvas group's alpha back to 1 and then once the power up is used set it back to 0.
I'm not seeming to have any luck with my current code.
// Super Jump pickup
if (col.gameObject.tag == "Spring")
{
superJumpText.GetComponent<CanvasGroup>().alpha = 1;
canSuperJump = true;
canSuperSpeed = false;
col.gameObject.SetActive(false);
hitSuperJump = true;
Invoke("Display", 20);
}
void Update()
{
//super jump
if (canSuperJump)
{
if (Input.GetKeyDown(KeyCode.Mouse0))
{
Vector3 jump = new Vector3(0, superJumpForce, 0);
GetComponent<Rigidbody>().AddForce(jump);
canSuperJump = false;
superJumpText.GetComponent<CanvasGroup>().alpha = 0;
}
}
}
The only reason this would not work for you is that somewhere in your parent hierarchy you have a gameobject with another canvas group. That would ALWAYS override the child canvas group settings UNLESS you select the 'Ignore Parent Groups' checkbox in the child canvas

Resources