Word Wrap in Java Swing with Hard Wrap Pixel Limit - java

I am having issues implementing a word wrap in java in a swing enviroment. I have a hard limit in pixels that I can't go over. I feel like I am close as I have found a solution that wraps, but it lets the last word run over the limit before going to the next line.
'ni' is a buffered image that this is all being drawn to.
'theSettings' custom class that contains variables for the text.
'theText' is the string that needs to be wrapped.
This is what is getting passed to the method:
FontMetrics fm = ni.createGraphics().getFontMetrics(theSettings.getFont());
//Get pixel width of string and divide by # of characters in string to get estimated width of 1 character
int charPixelWidth = fm.stringWidth(theText) / theText.length();
int charWrap = theSettings.getPixelsTillWrap() / charPixelWidth;
List<String> textList = testWordWrap(theText, charWrap);
I am using this method to test:
//Custom String Wrapper as StringUtils.wrap keeps cutting words in half
public static List<String> testWordWrap(String wrapMe, int wrapInChar) {
StringBuilder sb = new StringBuilder(wrapMe);
int count = 0;
while((count = sb.indexOf(" ", count + wrapInChar)) != -1) {
sb.replace(count, count + 1, "\n");
}
String toArray = sb.toString();
String[] returnArray = toArray.split("\\n");
ArrayList<String> returnList = new ArrayList<String>();
for(String s : returnArray) {
returnList.add(s);
}
return returnList;
}
Example in use:
I also replace images in the middle of the text, but the size of the image is exactly the size of the text that it is replacing; so that shouldn't be an issue. The blue lines are bounding boxes that show where the wrap should be ending. It shows that the text continues to draw. I need the function to wrap 'succeeded' to the next line; as the wrap can't run over in any situation.
EDIT: The arrays of the image run as .toString();
[Resolution: +|FT| if you succeeded, and matched a |EA|]
[Any: +|MT| when you gain any number, of |QT|]

I was able to identify an answer. I split the string on spaces and then try adding each word one at a time. If it is too long, jump to the next line. Here it is over commented to help anyone else with this same issue. Cheers!
//#Author Hawox
//split string at spaces
//for loop checking to see if adding that next word moves it over the limit, if so go to next line; repeat
public static List<String> wordWrapCustom(String wrapMe, FontMetrics fm, int wrapInPixels){
//Cut the string into bits at the spaces
String[] split = wrapMe.split(" ");
ArrayList<String> lines = new ArrayList<String>(); //we will return this, each item is a line
String currentLine = ""; //All contents of the currentline
for(String s : split) {
//Try to add the next string
if( fm.stringWidth(currentLine + " " + s) >= wrapInPixels ) {
//Too big
if(currentLine != "") { //If it is still bigger with an empty string, still add at least 1 word
lines.add(currentLine); //Add the line without this string
currentLine = s; //Start next line with this string in it
continue;
}
}
//Still have room, or a single word that is too big
//add a space if not the first word
if(currentLine != "")
currentLine = currentLine + " ";
//Append string to line
currentLine = currentLine + s;
}
//The last line still may need to get added
if(currentLine != "") //<- Should not get called, but just in case
lines.add(currentLine);
return lines;
}

Related

Problem creating loop for repeat search option, advice for underlining search query occurrences

I'm running into a problem writing a search method that iterates through an array list of lines of text and repeats the search.
In addition, I'm also running into a wall trying to create a loop that underscores the occurrences of the search query in the line. Like so:
Enter Search Pattern: he
Line number 1
Her old collecting she considered discovered.
^
Line number 2
So at parties he warrant oh staying. Square new horses and put better end.
^
Line number 8
Pursuit he he garrets greater towards amiable so placing.
^ ^
Line number 9
Nothing off how norland delight. Abode shy shade she hours forth its use.
^
I've been able to code the method to search a line(it returns the text above as expected), but I'm hitting the wall trying to get the search to repeat, that is, prompt the user for additional search queries to perform another search.
Also, any clue how to do the highlighting with the ^ like above? I haven't been able to find anything to help me.
Here is my code for the method. I've included the main, and search method.
public static void main(String[] args)throws IOException{
if(args.length == 0){
System.out.println("Error, usage: java ClassName inputfile");
System.exit(1);
}
File randomText = new File(args[0]);
if(randomText.exists() && randomText.isFile()){
processFile(randomText);
} else{
System.err.println("ERROR: file does not exist");
System.exit(1);
}
//there are more methods to this program, like to call the search method, I just included the code in question.
}
public static void search(ArrayList<String> lineList){
Scanner userInput = new Scanner(System.in);
System.out.println("Enter Search Pattern: ");
String searchQuery = userInput.nextLine();
int i=0;
for (String line : lineList){
i++;
if(line.contains(searchQuery)){
System.out.println("Line number " + i);
System.out.println(line);
}
}
}
I'm not sure that it will always line up perfectly, but this should work. I'm trying to build a new highlight string, which has a space for every character in line apart from the start of the matching searchQuery where the character is ^.
You can find the index of your matching searchQuery with indexOf.
public static String padString(int start, int index){
String pad = new String();
for(int i=start; i<index; i++){
pad = pad + " ";
}
return pad;
}
public static String getHighlight(String line, String searchQuery){
String highlight = new String();
int index = line.indexOf(searchQuery);
while(index >= 0){
highlight = highlight + padString(highlight.length(), index) + "^";
index = line.indexOf(searchQuery, index+1);
}
return highlight;
}
public static void search(ArrayList<String> lineList){
Scanner userInput = new Scanner(System.in);
System.out.println("Enter Search Pattern: ");
String searchQuery = userInput.nextLine();
int i=0;
for (String line : lineList){
i++;
if(line.contains(searchQuery)){
System.out.println("Line number " + i);
System.out.println(line);
System.out.println(getHighlight(line, searchQuery));
}
}
}
The key to this sort of thing working properly is the fact that the font being used to display the file line Strings and the the possible locator strings (...^...) is of a Monospaced type font. All characters within a Monospaced font are of the same fixed size and therefore the letters and characters of that font each occupy the same amount of horizontal space. Examples of such fonts are:
Andalé Mono Cascadia Code Consolas Courier
Cutive Mono DejaVu Sans Mono Droid Sans Mono Everson Mono
Fira Mono Fixed Fixedsys Go Mono
HyperFont IBM Plex Mono Inconsolata Iosevka
Letter Gothic Liberation Mono Lucida Console Menlo
Monaco Monofur Monospace (Unicode) Nimbus Mono L
Noto Mono OCR-A OCR-B Overpass Mono
Oxygen Mono PragmataPro Prestige Elite ProFont
PT Mono Roboto Mono Source Code Pro Terminus
Tex Gyre Cursor UM Typewriter
There are more. Do a search to for Monospaced Typefaces for a complete list. The bottom line is, if a monospaced font is used then you can be assured that the locator character (^) will always be at the exact start location where your desired search string(s) are found.
In order to continuously do searches, you will need to wrap your call to the search() method in a loop like a while loop for example. You should then also modify the search() method to return a String, for example, what was entered into the prompt but only if it is the letter 'q' (for quit) otherwise return nothing (null string ("")). With this is play you can do something like this:
// Continuously do one search after another until
// q is supplied as a search query
while (true) {
if (search(list, true).equalsIgnoreCase("q")) {
break; // Break out of the 'while' loop
}
System.out.println();
}
System.out.println("<< DONE >>");
The search() method code below allows for letter case to be ignored if required. If the ArrayList contains:
ArrayList<String> list = new ArrayList<>();
list.add("Her old collecting she considered discovered. ");
list.add("So at parties he warrant oh staying. Square new horses and put better end.");
list.add("Pursuit he he garrets greater towards amiable so placing.");
then just call search(list, true);. If he was supplied at the search criteria prompt then the output result will be:
Enter Search Pattern: --> he
Line number 1
Her old collecting she considered discovered.
^ ^
Line number 2
So at parties he warrant oh staying. Square new horses and put better end.
^
Line number 3
Pursuit he he garrets greater towards amiable so placing.
^ ^
Here is the new search() method:
public static String search(ArrayList<String> lineList, boolean ignoreLetterCase) {
Scanner userInput = new Scanner(System.in);
System.out.print("Enter Search Pattern (q to quit): --> ");
String searchQuery = userInput.nextLine();
if (searchQuery.equalsIgnoreCase("q")) {
return "q";
}
int lineCounter = 0;
for (String line : lineList) {
lineCounter++;
// Generate the 'location' string. Returns null
// if the search item was not found!
String found = getFoundDisplayLine(line, searchQuery, ignoreLetterCase);
if (found != null) {
System.out.println("Line number " + lineCounter);
System.out.println(line);
System.out.println(found);
}
}
return "";
}
And here is the getFoundDisplayLine() method which generates the 'location' string:
public static String getFoundDisplayLine(String inputString, String queryString, boolean... ignoreLetterCase) {
if (inputString == null || inputString.trim().isEmpty()) {
return null;
}
String wrkStrg = inputString;
String findStrg = queryString;
boolean ignoreCase = false;
if (ignoreLetterCase.length > 0) {
ignoreCase = ignoreLetterCase[0];
}
if (ignoreCase) {
wrkStrg = wrkStrg.toLowerCase();
findStrg = queryString.toLowerCase();
}
String indexes = "";
List<Integer> idx = new ArrayList<>();
int index = wrkStrg.indexOf(findStrg);
while (index >= 0) {
idx.add(index);
indexes += (indexes.isEmpty()) ? String.valueOf(index) : ", " + String.valueOf(index);
index = wrkStrg.indexOf(findStrg, index + findStrg.length());
}
if (idx.isEmpty()) {
return null;
}
StringBuilder sb = new StringBuilder("");
for (int i = 0; i < inputString.length(); i++) {
if (idx.contains(i)) {
sb.append("^");
}
else {
sb.append(" ");
}
}
return sb.toString();
}

Java Scanner Function - How do I grab a certain word or words from inside a string?

So, I'm reading in a line of text into a scanner variable, an example looks something like this:
Scanner strLine = new Scanner("Peter has red hair and is very tall");
I want to then pull out certain aspects of the string and apply it to individual String variables:
String name = "Peter"
String hairColor = "red hair"
String height = "very tall"
I've tried 'for' statements that look for the words "has" and then grab the next 2 words with:
String hairColor = strLine.next() + strLine.next();
Is there an easier way to read in a line of text and pull individual information out of it other than Scanner?
Thank you.
I would use split and then dump the results into a List
String text = "Peter has red hair and is very tall";
List<String> words = Arrays.asList(text.split(" "));
int hasIndex = words.indexOf("has");
if (hasIndex != -1) {
System.out.println(words.get(hasIndex + 1) + " " + words.get(hasIndex + 2));
}
Of course care should be taken for Out Of Bounds Errors

if statement not adding value to my counter in word count program

I have a java program that reads a txt file and counts the words in that file. I setup my program so the String read from the txt file is saved as an ArrayList, and my variable word contains that ArrayList. The issue with my code is that my if statement does not seem to add a value to my count variable each time it detects space in the word string, it seems to only run the if statement once. How can I make it so the if statement finds a space, adds a +1 to my counter value, removes the space, and looks for the next space in the word variable's string? Here is the code:
import java.io.*;
import java.util.*;
public class FrequencyCounting
{
public static void main(String[] args) throws FileNotFoundException
{
// Read-in text from a file and store each word and its
// frequency (count) in a collection.
Scanner inputFile = new Scanner(new File("phrases.txt"));
String word= " ";
Integer count = 0;
List<String> ma = new ArrayList<String>();
while(
inputFile.hasNextLine()) {
word = word + inputFile.nextLine() + " ";
}
ma.add(word);
System.out.println(ma);
if(word.contains(" ")) {
ma.remove(" ");
count++;
System.out.println("does contain");
}
else {
System.out.println("does not contain");
}
System.out.println(count);
//System.out.println(ma);
inputFile.close();
// Output each word, followed by a tab character, followed by the
// number of times the word appeared in the file. The words should
// be in alphabetical order.
; // TODO: Your code goes here.
}
}
When I execute the program, I get a value of 1 for the variable count and I get a returned string representation of the txt file from my phrases.txt
phrases.txt is :
my watch fell in the water
time to go to sleep
my time to go visit
watch out for low flying objects
great view from the room
the world is a stage
the force is with you
you are not a jedi yet
an offer you cannot refuse
are you talking to me
Your if statement is not inside any loop, so it will only execute once.
A better approach, which would save a shit ton of runtime, is to read each line like you already do, use the String.split() method to split it on spaces, then add each element of the returned String[] to your list by using the ArrayList.addAll() method (if that one exist, otherwise (optionally, ensure the capacity and) add the elements one by one).
Then count by using the ArrayList.size() method to get the number of elements.
Based on the comments in your code :
// Read-in text from a file and store each word and its
// frequency (count) in a collection.
// Output each word, followed by a tab character, followed by the
// number of times the word appeared in the file. The words should
// be in alphabetical order.
My understanding is that you need to store count for every word, rather having a total count of words. For storing count for every word which should be stored itself in alphabetical order, it is better to go with a TreeMap.
public static void main(String[] args) {
Map<String, Integer> wordMap = new TreeMap<String, Integer>();
try {
Scanner inputFile = new Scanner(new File("phrases.txt"));
while(inputFile.hasNextLine()){
String line = inputFile.nextLine();
String[] words = line.split(" ");
for(int i=0; i<words.length; i++){
String word = words[i].trim();
if(word.length()==0){
continue;
}
int count = 0;
if(wordMap.containsKey(word)){
count = wordMap.get(word);
}
count++;
wordMap.put(word, count);
}
}
inputFile.close();
for(Entry<String,Integer> entry : wordMap.entrySet()){
System.out.println(entry.getKey()+"\t"+entry.getValue());
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
What is your goal here ? Do you just want to read the file and count numbers of words?
You need to use a while loop instead of an if statement that'll just run once. Here's a better way to do what you want to do:
Scanner inputFile = new Scanner(new File("phrases.txt"));
StringBuilder sb = new StringBuilder();
String line;
int totalCount = 0;
while(inputFile.hasNextLine()) {
line = inputFile.nextLine();
sb.append(line).append("\n"); // This is more efficient than concatenating strings
int spacesOnLine = countSpacesOnLine(line);
totalCount += spacesOnLine;
// print line and spacesOnLine if you wish to here
}
// print text file
System.out.println(sb.toString());
// print total spaces in file
System.out.println("Total spaces" + totalCount);
inputFile.close();
Then add a method that counts the spaces on a line:
private int countSpacesOnLine(String line) {
int totalSpaces = 0;
for(int i = 0; i < line.length(); i++) {
if (line.charAt(i) == ' ')
totalSpaces += 1;
}
return totalSpaces;
}
You can achieve your objective with the following one liner too:
int words = Files.readAllLines(Paths.get("phrases.txt"), Charset.forName("UTF-8")).stream().mapToInt(string -> string.split(" ").length).sum();
probably I am late, but here is c# simple version:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace StackOverflowAnswers
{
class Program
{
static void Main(string[] args)
{
string contents = File.ReadAllText(#"C:\temp\test.txt");
var arrayString = contents.Split(' ');
Console.WriteLine("Number of Words {0}", arrayString.Length);
Console.ReadLine();
}
}
}

Java - Importing text file into array when lines are not consistent

I have an assignment that pretty much has me stumped early on, the remainder of which is fairly easy (sorting the data once its imported and then saving it again under a different name).
We need to import data from a .txt file into 3 separate Arrays ( name, mascot, alias ) however the lines are not consistent. By consistent I mean one line may have:
Glebe,G Shield,Glebe District
While another line may have:
St George,Knight & Dragon,Saints,Dragons,St George Illawarra
Everything before the first , belongs to the name array.
Everything after the first , but before the second , belongs to the mascot array.
Everything after the second , till the end of the line belongs to the alias array.
I've been able to work out how to import the .txt file where it contains the entire line, which I was then able to convert into importing everything before a "," and new line (using Delimiters). However the lines that contain more then 3 sets of data ruin the import as the alias array only ends up holding 1 not everything else.
Thus does anyone know of and can show me a code that pretty much does:
name = Everything before the first ,
Mascot = Everything after the first , but before the second ,
Alias = Everything after the second , till the end of the line
That I could use as a base to work into mine?
After a day of research I've constantly come up with dead ends. They all generally involve splitting up at each comma but that breaks the import (lines with more then 1 alias, the second alias is put into the name array, ect)
This is the code I came up with that imports the entire line into an array:
public static void LoadData() throws IOException
{
String clubtxt = ("NRLclubs.txt");
String datatxt = ("NRLdata.txt");
int i, count;
File clubfile = new File(clubtxt);
File datafile = new File(datatxt);
if (clubfile.exists())
{
count = 0;
Scanner inputFile = new Scanner(clubfile);
i = 0;
while(inputFile.hasNextLine())
{
count++;
inputFile.nextLine();
}
String [] teamclub = new String[count];
inputFile.close();
inputFile = new Scanner(clubfile);
while(inputFile.hasNext())
{
teamclub[i] = inputFile.nextLine();
System.out.println(teamclub[i]);
i++;
}
inputFile.close();
}
else
{
System.out.println("\n" + "The file " + clubfile + " does not exist." + "\n");
}
if (datafile.exists())
{
count = 0;
Scanner inputFile = new Scanner(datafile);
i = 0;
while(inputFile.hasNextLine())
{
count++;
inputFile.nextLine();
}
String [] teamdata = new String[count];
inputFile.close();
inputFile = new Scanner(datafile);
while(inputFile.hasNext())
{
teamdata[i] = inputFile.nextLine();
System.out.println(teamdata[i]);
i++;
}
inputFile.close();
}
else
{
System.out.println("\n" + "The file " + datafile + " does not exist." + "\n");
}
}
Look at String.split method with the parameter limit.
When you have your input line in a variable called line, you can can call
String[] tokens = line.split(',', 3);
This will split the line on the commas, while making sure that it will not return more than 3 tokens. It returns an array of String in which the first element will be what is before the first comma, the second will be what is between the first and second commas, and the third element will be what is after the second comma.
Since you only want to parse on the first 2 commas, you can use String split with a limit.
If you prefer, you can use the String indexOf method to find the first 2 commas, then use the String substring method to get the characters between the commas.
You want to be able to handle a line with one comma, or no commas at all.
Here's one way to parse the String line
public List<String> splitLine(String line) {
List<String> list = new ArrayList<String>();
int firstPos = line.indexOf(",");
int secondPos = line.indexOf(",", firstPos + 1);
if (firstPos >= 0) {
if (secondPos >= 0) {
list.add(line.substring(0, firstPos));
list.add(line.substring(firstPos + 1, secondPos));
list.add(line.substring(secondPos + 1));
} else {
list.add(line.substring(0, firstPos));
list.add(line.substring(firstPos + 1));
list.add("");
}
} else {
list.add(line);
list.add("");
list.add("");
}
return list;
}
You can use the String.split method.
String line = // the line you read here
// Split on commas but only make three elements
String[] elements = line.split(',', 3);
// The first belongs to names
names[linecount] = elements[0];
// The second belongs to mascot
mascot[linecount] = elements[1];
// And the last belongs to aliases
aliases[linecount] = elements[2];
Try looking into the Pattern/Matcher stuff -- you need to come up with an appropriate regex.
http://docs.oracle.com/javase/6/docs/api/java/util/regex/Pattern.html
Something like this might do it:
static final Pattern pattern = Pattern.compile("([^,]*),([^,]*),(*$)");
MatchResult result = pattern.matcher(line).toMatchResult();
if (result.groupCount() == 3) {
// Found the groups
name = result.group(0);
// etc..
} else {
// failed to match line
}
Basically what you want to do is split each line into an array as you read it in, and then parse the data line by line. Something like this (pseudocode):
Scanner inputFile = new Scanner(datafile);
while(inputFile.hasNextLine()) {
String line = inputFile.nextLine();
String[] lineSplit = line.split(",");
//TODO: make sure lineSplit is at least 3 long.
String name = lineSplit[0];
String mascot = lineSplit[1];
//EDIT: Don't just get the last element, get everything after the first two.
// You can do this buy just getting the substring of the length of those two strings
// + 2 to account for commas.
//String alias = lineSplit[lineSplit.length() - 1];
String alias = line.substring(name.length() + mascot.length() + 2);
//If you need to do trimming on the strings to remove extra whitespace, do that here:
name = name.trim();
mascot = mascot.trim();
alias = alias.trim();
//TODO: add these into the arrays you need.
}
Hope this helps.

Getting words from a file Inputs

I am trying to take in words from a file input that only contains strings and store each word separately into a single array (ArrayList is not allowed).
My code below takes in the file input, but takes it in as one chunk. For example, if the file input was "ONE TWO THREE" I want each word to have its own index in the array (array[0] = "ONE", array[1]="TWO" and array[2]="THREE") but my code below just takes the sentence and puts it all in array[0] = "ONE TWO THREE". How can I fix this?
int i = 0;
String wd = "";
while (in.hasNextLine() ) {
wd = in.nextLine() ;
array[i] = wd;
i++;
System.out.println("wd");
}
Thats happens because you a reading LINES from the file. You can do something like this:
String array[]=in.nextLine().split(" ");
f the file input was " ONE TWO THREE" i want each word to have its own
space in the array so array[0] = ONE, array1=TWO and array[2]=THREE
Use String#split(delim) with white space as delimiter.
String fileInput = "ONE TWO THREE";
String[] array = filrInput.split("\\s");
Instead of the line
array[i] = wd; // since wd is the whole line, this puts lines in the array
you want to split the line you read in and put the split items in your array.
String[] items = wd.split("\\s+"); // splits on any whitespace
for (String item : items) { // iterate through the items and shove them in the array
array[i] = item;
i++;
}
I would set the delimiter to space " " then use .next().
in.useDelimiter(" ");
while(in.hasNext()){
wd = in.next();
//wd will be one word
}

Categories