Change PDFbox font of field text - android

I'm writing an app that uses PDFbox library to fill fields in a PDF file.
In one of those field, I'm setting the text to be written in Hebrew letters.
When I run the code on my Android device, I get the following log:
java.lang.IllegalArgumentException: This font type only supports 8-bit code points
at com.tom_roush.pdfbox.pdmodel.font.PDType1Font.encode(PDType1Font.java:317)
at com.tom_roush.pdfbox.pdmodel.font.PDFont.encode(PDFont.java:264)
at com.tom_roush.pdfbox.pdmodel.font.PDFont.getStringWidth(PDFont.java:293)
at com.tom_roush.pdfbox.pdmodel.interactive.form.PlainTextFormatter.format(PlainTextFormatter.java:183)
at com.tom_roush.pdfbox.pdmodel.interactive.form.AppearanceGeneratorHelper.insertGeneratedAppearance(AppearanceGeneratorHelper.java:360)
at com.tom_roush.pdfbox.pdmodel.interactive.form.AppearanceGeneratorHelper.setAppearanceContent(AppearanceGeneratorHelper.java:224)
at com.tom_roush.pdfbox.pdmodel.interactive.form.AppearanceGeneratorHelper.setAppearanceValue(AppearanceGeneratorHelper.java:128)
at com.tom_roush.pdfbox.pdmodel.interactive.form.PDTextField.constructAppearances(PDTextField.java:247)
at com.tom_roush.pdfbox.pdmodel.interactive.form.PDTerminalField.applyChange(PDTerminalField.java:221)
at com.tom_roush.pdfbox.pdmodel.interactive.form.PDTextField.setValue(PDTextField.java:202)
at com.package.app.MainActivity.lambda$checkPdf$4$MainActivity(MainActivity.java:128)
at com.package.app.MainActivity$$Lambda$2.run(Unknown Source:18)
at java.lang.Thread.run(Thread.java:764)
I've tried to find some information about it all over Stack Overflow, but none of the answers I found is related to filling forms. It's all related to PDPageContentStream.
This is how I fill the form in my code:
try {
PDDocument document = PDDocument.load(getAssets().open("file.pdf"));
PDDocumentCatalog docCatalog = document.getDocumentCatalog();
PDAcroForm acroForm = docCatalog.getAcroForm();
// Fill the text field
((PDTextField) acroForm.getField("name")).setValue("בדיקה");
File root = android.os.Environment.getExternalStorageDirectory();
String path = root.getAbsolutePath() + "/test.pdf";
document.save(path);
document.close();
} catch (IOException e) {
Log.e("e", e.getMessage());
}
Can you please help me solve this error and fill Hebrew letters in a form using PDFbox?

I used this answer to change the font of the field's text.
The only problem is that now the text was facing the wrong direction, so I changed the direction of the string:
try {
PDDocument document = PDDocument.load(getAssets().open("file.pdf"));
PDDocumentCatalog docCatalog = document.getDocumentCatalog();
PDAcroForm acroForm = docCatalog.getAcroForm();
PDResources dr = acroForm.getDefaultResources();
PDFont liberationSans = PDType0Font.load(document, getAssets().open("com/tom_roush/pdfbox/resources/ttf/LiberationSans-Regular.ttf"));
COSName fontName = dr.add(liberationSans);
Iterator<PDField> it = acroForm.getFields().iterator();
while (it.hasNext()) {
PDField field = it.next();
if (field instanceof PDTextField) {
PDTextField textField = (PDTextField) field;
String da = textField.getDefaultAppearance();
// replace font name in default appearance string
Pattern pattern = Pattern.compile("\\/(\\w+)\\s.*");
Matcher matcher = pattern.matcher(da);
String oldFontName = matcher.group(1);
da = da.replaceFirst(oldFontName, fontName.getName());
textField.setDefaultAppearance(da);
}
}
// Fill the text field
((PDTextField) acroForm.getField("name")).setValue(new StringBuilder("בדיקה").reverse().toString());
File root = android.os.Environment.getExternalStorageDirectory();
String path = root.getAbsolutePath() + "/test.pdf";
document.save(path);
document.close();
} catch (IOException e) {
Log.e("e", e.getMessage());
}

Related

Is there a way to export realm database to CSV/JSON?

I want to export my realm database to CSV/JSON in Android. Is there some in-build method in the realm database which can do this?
There is a iOS way of converting realm to CSV link. I want a similar method in Android.
I was able to cobble together the following solution in my project:
// Grab all data from the DB in question (TaskDB):
RealmResults<TaskDB> resultsDB = realm.where(TaskDB.class).findAll();
// Here we need to put in header fields
String dataP = null;
String header = DataExport.grabHeader(realm, "TaskDB");
// We write the header to file
savBak(header);
// Now we write all the data corresponding to the fields grabbed above:
for (TaskDB taskitems: resultsDB) {
dataP = taskitems.toString();
// We process the data obtained and add commas and formatting:
dataP = dataProcess(dataP);
// Workaround to remove the last comma from final string
int total = dataP.length() - 1;
dataP = dataP.substring(0,total);
// We write the data to file
savBak(dataP);
}
I will explain what it is doing as best I can and include all corresponding code(all in reference to the first code block).
The first I did is grab the header using the following method I wrote in a separate class (DataExport.grabHeader). It takes 2 arguments: the realm object in question and the DB object model name:
public static String grabHeader(Realm realm, String model){
final RealmSchema schema = realm.getSchema();
final RealmObjectSchema testSchema = schema.get(model);
final String header = testSchema.getFieldNames().toString();
String dataProcessed = new String();
Pattern p = Pattern.compile("\\[(.*?)\\]");
Matcher m = p.matcher(header);
while(m.find()) {
dataProcessed += m.group(1).trim().replaceAll("\\p{Z}","");
}
return dataProcessed;
Within grabHeader, I apply some regex magic and spit out a string that will be used as the header with the appropriate commas in place (String dataProcessed).
In this scenario, after I obtained the data needed, I used another method (savBak) to write the information to a file which takes 1 string argument:
#Override
public void savBak(String data){
FileOutputStream fos = null;
try {
fos = openFileOutput(FILE_NAME, MODE_PRIVATE | MODE_APPEND);
fos.write(data.getBytes());
fos.write("\n".getBytes());
Log.d("tester", "saved to: " + getFilesDir() + "/" + FILE_NAME);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
The "savBak" method writes the information to a FILE_NAME specified in a variable and we have our header information. After the header is written, we do the basically the same process with the DB using a forloop but I also had to include 2 lines to remove the trailing comma after the line was processed. Each line is appended to the file and viola, CSV formatted goodness.
From here, you can use other existing methods of converting CSV to JSON and whatever else as well as putting the information back into realm via JSON. When it comes to more advanced elements like primary keys and such, I am not sure but it worked for my particular project needs.
Please excuse any "bad code" practice as I'm new to Java/Android in general coming from a "barely intermediate" Python background so hopefully this makes sense.
I got a reply from Realm support via email.
Unfortunately, we do not have this feature yet. You can see it tracked here: https://github.com/realm/realm-java/issues/2880
You could use a dynamic API and write a script yourself to perform a similar feature.

Inline CSS for hr not applied while converting html to pdf using iText library

I am using Itext library for android for converting html to pdf which is working fine but at certain things it is not parsing properly. I want to create a dotted line separator of red color but it is always gives me a solid line separator with dark gray color.
My html tag is
<hr noshade style="border: 0; width:100%;border-bottom-width: 1px; border-bottom-style: dotted; border-bottom-color: red">
My conversion code
Document document = new Document(PageSize.A4);
//this sets the margin to the created pdf
document.setMargins(35, 35, 150, 100);
PdfWriter writer = PdfWriter.getInstance(document,
new FileOutputStream(fileWithinMyDir));
if (isPrescription) {
HeaderFooterPageEvent event = new HeaderFooterPageEvent();
writer.setPageEvent(event);
} else {
CertificateFooterPageEvent event = new CertificateFooterPageEvent();
writer.setPageEvent(event);
}
document.open();
HtmlPipelineContext htmlContext = new HtmlPipelineContext(null);
htmlContext.setTagFactory(Tags.getHtmlTagProcessorFactory());
htmlContext.setImageProvider(new AbstractImageProvider() {
public String getImageRootPath() {
Uri uri = Uri.parse("file:///android_asset/");
return uri.toString();
}
});
CSSResolver cssResolver =
XMLWorkerHelper.getInstance().getDefaultCssResolver(false);
// Pipelines
PdfWriterPipeline pdf = new PdfWriterPipeline(document, writer);
HtmlPipeline html = new HtmlPipeline(htmlContext, pdf);
CssResolverPipeline css = new CssResolverPipeline(cssResolver, html);
XMLWorker worker = new XMLWorker(css, true);
XMLParser p = new XMLParser(worker);
InputStream is = new ByteArrayInputStream(htmlString.getBytes());
XMLWorkerHelper.getInstance().parseXHtml(writer, document, is);
p.parse(is);
document.close();
I'm a .NET developer, so the code is in C#. But you should be able to easily translate the following.
iText is a PDF-first library, and [X]HTML parsing is quite complex so it's not full featured in that regard. Whenever parsing [X]HTML and things aren't going the way you expect for specific tags, the basic steps you should follow are:
Verify XML Worker supports the tag: Tags class.
If the tag is supported, which in this case is true, take a look at the default implementation. Here it's handled by the the HorizontalRule class. However, we see there's no support for your use case, so one way to go is use that code as a blueprint. (follows below) You can also inherit from the specific tag class and override the End() method as done here. Either way, all you're doing is implementing a custom tag processor.
If the tag is not supported, you need to roll your own custom tag processor by inheriting from AbstractTagProcessor.
Anyway, here's a simple example to get you started. First, the custom tag processor:
public class CustomHorizontalRule : AbstractTagProcessor
{
public override IList<IElement> Start(IWorkerContext ctx, Tag tag)
{
IList<IElement> result;
LineSeparator lineSeparator;
var cssUtil = CssUtils.GetInstance();
try
{
IList<IElement> list = new List<IElement>();
HtmlPipelineContext htmlPipelineContext = this.GetHtmlPipelineContext(ctx);
Paragraph paragraph = new Paragraph();
IDictionary<string, string> css = tag.CSS;
float baseValue = 12f;
if (css.ContainsKey("font-size"))
{
baseValue = cssUtil.ParsePxInCmMmPcToPt(css["font-size"]);
}
string text;
css.TryGetValue("margin-top", out text);
if (text == null) text = "0.5em";
string text2;
css.TryGetValue("margin-bottom", out text2);
if (text2 == null) text2 = "0.5em";
string border;
css.TryGetValue(CSS.Property.BORDER_BOTTOM_STYLE, out border);
lineSeparator = border != null && border == "dotted"
? new DottedLineSeparator()
: new LineSeparator();
var element = (LineSeparator)this.GetCssAppliers().Apply(
lineSeparator, tag, htmlPipelineContext
);
string color;
css.TryGetValue(CSS.Property.BORDER_BOTTOM_COLOR, out color);
if (color != null)
{
// WebColors deprecated, but docs don't state replacement
element.LineColor = WebColors.GetRGBColor(color);
}
paragraph.SpacingBefore += cssUtil.ParseValueToPt(text, baseValue);
paragraph.SpacingAfter += cssUtil.ParseValueToPt(text2, baseValue);
paragraph.Leading = 0f;
paragraph.Add(element);
list.Add(paragraph);
result = list;
}
catch (NoCustomContextException cause)
{
throw new RuntimeWorkerException(
LocaleMessages.GetInstance().GetMessage("customcontext.404"),
cause
);
}
return result;
}
}
Most of the code is taken directly from the existing source, with the exception of the checks for CSS.Property.BORDER_BOTTOM_STYLE and CSS.Property.BORDER_BOTTOM_COLOR to set border style and color if they're inlined in the <hr> style attribute.
Then you add the custom tag processor above to the XML Worker TagProcessorFactory:
using (var stream = new FileStream(OUTPUT_FILE, FileMode.Create))
{
using (var document = new Document())
{
var writer = PdfWriter.GetInstance(document, stream);
document.Open();
var tagProcessorFactory = Tags.GetHtmlTagProcessorFactory();
// custom tag processor above
tagProcessorFactory.AddProcessor(
new CustomHorizontalRule(),
new string[] { HTML.Tag.HR }
);
var htmlPipelineContext = new HtmlPipelineContext(null);
htmlPipelineContext.SetTagFactory(tagProcessorFactory);
var pdfWriterPipeline = new PdfWriterPipeline(document, writer);
var htmlPipeline = new HtmlPipeline(htmlPipelineContext, pdfWriterPipeline);
var cssResolver = XMLWorkerHelper.GetInstance().GetDefaultCssResolver(true);
var cssResolverPipeline = new CssResolverPipeline(
cssResolver, htmlPipeline
);
var worker = new XMLWorker(cssResolverPipeline, true);
var parser = new XMLParser(worker);
var xHtml = "<hr style='border:1px dotted red' />";
using (var stringReader = new StringReader(xHtml))
{
parser.Parse(stringReader);
}
}
}
One thing to note is that even though we're using the shorthand border inline style, iText's CSS parser appears to set all the styles internally. I.e., you can use any of the four longhand styles to check - I just happened to use CSS.Property.BORDER_BOTTOM_STYLE and CSS.Property.BORDER_BOTTOM_COLOR.
The resulting PDF:
You could use a div without any or with any content you want instead of an hr and give border style to that div, I am sure it will work in your case.

Can I remove the symbol new line from base 64 encoded key file?

I have a base 64 encoded key file. If I open it by Text Editor, I see 4 lines like this:
Then I copy the text and paste to Android Studio, I see the symbol "\n" is generated as below:
This pubic key doesn't work. So I tried :
Remove all "\n" symbol. Still doesn't work.
Replace the "\n" symbol with the space " ". Again doesn't work.
Could you please show me where I am wrong?
Rather than pasting the contents of the file into a string, why not just copy the file itself into your assets folder. For example:
public String readPublicKeyFromFile() {
String publicKeyString; = "";
try {
InputStream is = getAssets().open("public_key.txt");
byte[] buffer = new byte[size];
is.read(buffer);
is.close();
// Convert the buffer into a string.
return new String(buffer);
} catch (IOException e) {
throw new RuntimeException(e);
}
return null;
}
Its android studio console character limitation that it shows long string in multiple lines.
Best way is to copy that string in any text editor(notepad) and make it single line string and then paste it to studio.
Another way is just delete that '\n' character from your string it will be single line string.
e.g.
private static final String = "abcdefgh" +
"ijklmnop" +
"qrstuvwxyz";
just remove '\n' character from your string.
If you creating the "publickey.txt" (base64) file, just use "Base64.NO_WRAP" flag for creating the file. This flag not allow the "\n" character.
By default it takes the "Base64.DEFAULT" flag, so every 64 characters after "\n" will be added automatically.
// for encoding the String with out \n
String base64Str=Base64.encode(your_string,Base64.NO_WRAP);
// for decoding
byte[] resByte=Base64.decode(base64Str,Base64.NO_WRAP);
// convert into String
String resStr=new String(resByte,"UTF-8");

Android Encoding for this character set

I am getting the html text from the website. this site return the character which like is shown in the below figure. I tried to find the character set from site, it found <meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
It show the output on device after set in the text view like:
I tried some coding but doesn't effect the text,which is shown below:
final Charset windowsCharset = Charset.forName("windows-1252");
final Charset utfCharset = Charset.forName("UTF-8");
final CharBuffer windowsEncoded = windowsCharset.decode(ByteBuffer
.wrap(ne.scrape_detail_article_text.getBytes()));
final byte[] utfEncoded = utfCharset.encode(windowsEncoded).array();
// System.out.println(new String(utfEncoded, utfCharset.displayName()));
String s = "" ;
try {
// String s = new String(utfEncoded, utfCharset.displayName());
//String s = new String(texttoencoding.getBytes("windows-1252"),"UTF-8");
s = URLEncoder.encode(texttoencoding, "windows-1252");
Log.e("LOG", "Encoded >> " + s);
} catch (UnsupportedEncodingException e) {
Log.e("utf8", "conversion", e);
}
TextviewToset.setText(Html.fromHtml(texttoencoding);
TextviewToset.setMovementMethod(LinkMovementMethod.getInstance());
Please Help me, how can I encode this text into UTF-8 And display in the textview?
Thanks in Advance
Looks like you are dealing with HTML-Entites here. Therefore you have to decode the HTML Entities via:
String text = HTML.fromHtml(yourText).toString();
This will give you the correct UTF-8 characters. The documentation for Html.fromHtml() is here

How to copy a document on Alfresco via Alfresco Mobile SDK for Android?

I am trying to copy a whole Folder.
To do so I am creating a new folder and putting all again inside it : folders and documents.
But I am getting an Alfresco Service Exception when trying to create a Document:
Newly created object is not a document !
My code is as below :
List<Document> documentsChildren = documentFolderService.getDocuments(folderToCopy);
for (Document document:documentsChildren){
ContentFile contentFileToCopy = documentFolderService.getContent(document);
String nameFileToCopy = document.getName();
// problem there
documentFolderService.createDocument(folderCopied, nameFileToCopy, properties, contentFileToCopy);
}
What is strange is that I yet implement the copy of a simple document inside a repository by the same way and it is working good:
Document documentToCopy = (Document) documentFolderService.getNodeByIdentifier(fileToCopy.getIdentifier());
ContentFile contentFileToCopy = documentFolderService.getContent(documentToCopy);
String nameFileToCopy = fileToCopy.getName();
documentFolderService.createDocument(folderParent, nameFileToCopy, properties, contentFileToCopy);
The only thing that changes is the way to obtain the document : by taking the children documents of a folder or by getting the document with his identifier.
Update of the question :
The exact message of error is:
Caused by: org.alfresco.mobile.android.api.exceptions.AlfrescoServiceException: Newly created object is not a document! New id: workspace://SpacesStore/85753128-1ac9-4b5e-b909-91dcb7d5ff5d
at org.alfresco.mobile.android.api.services.impl.AbstractDocumentFolderServiceImpl.createDocument(AbstractDocumentFolderServiceImpl.java:482)
properties is en empty map :
Map properties = new HashMap();
I test for .ppt documents, and as you ask the question, I also test for .docx and .jpg
the type of the node when the exception is raised is a folder
The quick fix that you proposed is not working.
Maybe I am doing a stupid error somewhere but I don't manage to find it.
Complete code :
List<Folder> foldersChildren = null;
List<Document> documentsChildren = null;
try {
ServiceRegistry serviceRegistry = Login.session.getServiceRegistry();
DocumentFolderService documentFolderService = serviceRegistry.getDocumentFolderService();
Folder folderParent = (Folder) documentFolderService.getNodeByIdentifier(repositoryParent.getIdentifier());
// create the folder
Map<String,Serializable> properties = new HashMap<String,Serializable>();
Folder folderToCopy = (Folder) documentFolderService.getNodeByIdentifier(repositoryToCopy.getIdentifier());
String nameRepositoryToCopy = repositoryToCopy.getName();
Folder folderCopied = documentFolderService.createFolder(folderParent, nameRepositoryToCopy, properties);
foldersChildren = documentFolderService.getFolders(folderToCopy);
documentsChildren = documentFolderService.getDocuments(folderToCopy);
// create the files inside the folder
ContentFile contentFileToCopy = null;
String nameFileToCopy = null;
for (Document document : documentsChildren){
contentFileToCopy = documentFolderService.getContent(document);
nameFileToCopy = document.getName();
// this operation doesn't work
documentFolderService.createDocument(folderCopied, nameFileToCopy, properties, contentFileToCopy);
}
} catch (Exception e){
this.cancel(true);
UIAlertDialog.runOnUIThreadOperationFailed(activity, context);
}
As the error message explained : Newly created object is not a document !
this error happens after the creation of the document when the method check if the current node is correctly created and have the right attributes.
I need more informations (stacktrace, type of the node...) to know if it's a bug on SDK side or a specific the method doesn't cover.
Could you provide informations
properties value : Is it an empty map ? the same map as the copied one ?
Does it happen for every document or just for some types of document ?
What's the type of the node when the exception is raised ?
But if the second method work, a quick fix could be :
List<Document> documentsChildren = documentFolderService.getDocuments(folderToCopy);
Document documentToCopy = null;
ContentFile contentFileToCopy = null;
String nameFileToCopy = null;
for (Document document : documentsChildren){
documentToCopy = (Document) documentFolderService.getNodeByIdentifier(document .getIdentifier());
contentFileToCopy = documentFolderService.getContent(documentToCopy);
nameFileToCopy = fileToCopy.getName();
documentFolderService.createDocument(folderParent, nameFileToCopy, properties, contentFileToCopy);
}
EDIT 06.02
Apparently there's an issue reference with the properties object inside documentFolderService.createFolder(). The map is not empty after this method and contains informations that disturb the next part of your code.
So to correct it (and if for you properties are not important during copy operation), please find the solution
List<Folder> foldersChildren = null;
List<Document> documentsChildren = null;
try {
ServiceRegistry serviceRegistry = Login.session.getServiceRegistry();
DocumentFolderService documentFolderService = serviceRegistry.getDocumentFolderService();
Folder folderParent = (Folder) documentFolderService.getNodeByIdentifier(repositoryParent.getIdentifier());
// create the folder
Folder folderToCopy = (Folder) documentFolderService.getNodeByIdentifier(repositoryToCopy.getIdentifier());
String nameRepositoryToCopy = repositoryToCopy.getName();
Folder folderCopied = documentFolderService.createFolder(folderParent, nameRepositoryToCopy, null);
foldersChildren = documentFolderService.getFolders(folderToCopy);
documentsChildren = documentFolderService.getDocuments(folderToCopy);
// create the files inside the folder
ContentFile contentFileToCopy = null;
String nameFileToCopy = null;
for (Document document : documentsChildren){
contentFileToCopy = documentFolderService.getContent(document);
nameFileToCopy = document.getName();
// this operation doesn't work
documentFolderService.createDocument(folderCopied, nameFileToCopy, null, contentFileToCopy);
}
} catch (Exception e){
this.cancel(true);
UIAlertDialog.runOnUIThreadOperationFailed(activity, context);
}
NB : Properties is an optional parameter so it can be null.

Categories

Resources