Ideal ellipsis truncation method

I'm sure we all saw the ellipsis on Facebook statuses (or elsewhere), and clicked "Show more", and there are only 2 more characters or so. I think this is because of lazy programming, because there is certainly an ideal method.

Mine counts thin characters [iIl1] as “half characters,” but that doesn't mean that the ellipsis looks stupid when they hide hardly any characters.

Is there an ideal method? Here is my:

 /** * Return a string with a maximum length of <code>length</code> characters. * If there are more than <code>length</code> characters, then string ends with an ellipsis ("..."). * * @param text * @param length * @return */ public static String ellipsis(final String text, int length) { // The letters [iIl1] are slim enough to only count as half a character. length += Math.ceil(text.replaceAll("[^iIl]", "").length() / 2.0d); if (text.length() > length) { return text.substring(0, length - 3) + "..."; } return text; } 

The language does not really matter, but is marked as Java, because it interests me the most.

+49
java ellipsis
Aug 30 2018-10-10T00:
source share
11 answers

I like the idea of ​​letting thin characters count half the character. Simple and good approximation.

The main problem with most ellipsing is: (imho) that they break words in the middle . Here is a solution that takes into account word boundaries (but does not dive into pixel math and Swing-API).

 private final static String NON_THIN = "[^iIl1\\.,']"; private static int textWidth(String str) { return (int) (str.length() - str.replaceAll(NON_THIN, "").length() / 2); } public static String ellipsize(String text, int max) { if (textWidth(text) <= max) return text; // Start by chopping off at the word before max // This is an over-approximation due to thin-characters... int end = text.lastIndexOf(' ', max - 3); // Just one long word. Chop it off. if (end == -1) return text.substring(0, max-3) + "..."; // Step forward as long as textWidth allows. int newEnd = end; do { end = newEnd; newEnd = text.indexOf(' ', end + 1); // No more spaces. if (newEnd == -1) newEnd = text.length(); } while (textWidth(text.substring(0, newEnd) + "...") < max); return text.substring(0, end) + "..."; } 

The algorithm test is as follows:

enter image description here

+70
Sep 07 '10 at 9:41
source share
— -

I'm shocked no one mentioned Commons Lang StringUtils # abbreviate () .

Update: yes, this does not apply to thin characters, but I do not agree that each of them has different settings for screens and fonts, and most of the people who land here on this page are probably looking for a supported library, as described above .

+40
01 Sep '12 at 13:57
source share

It looks like you can get more accurate geometry from the Java FontMetrics graphical context.

Appendix: As you approach this issue, this can help distinguish between a model and a view. The model is a String , the final sequence of UTF-16 code points, while the view is a series of glyphs displayed in some font on some device.

In the particular case of Java, you can use SwingUtilities.layoutCompoundLabel() to complete the translation. The following example intercepts a layout call in BasicLabelUI to demonstrate the effect. It may be possible to use the utility method in other contexts, but the corresponding FontMetrics should be determined empirically.

alt text

 import java.awt.Color; import java.awt.EventQueue; import java.awt.Font; import java.awt.FontMetrics; import java.awt.GridLayout; import java.awt.Rectangle; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import javax.swing.BorderFactory; import javax.swing.Icon; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.border.EmptyBorder; import javax.swing.border.LineBorder; import javax.swing.plaf.basic.BasicLabelUI; /** @see http://stackoverflow.com/questions/3597550 */ public class LayoutTest extends JPanel { private static final String text = "A damsel with a dulcimer in a vision once I saw."; private final JLabel sizeLabel = new JLabel(); private final JLabel textLabel = new JLabel(text); private final MyLabelUI myUI = new MyLabelUI(); public LayoutTest() { super(new GridLayout(0, 1)); this.setBorder(BorderFactory.createCompoundBorder( new LineBorder(Color.blue), new EmptyBorder(5, 5, 5, 5))); textLabel.setUI(myUI); textLabel.setFont(new Font("Serif", Font.ITALIC, 24)); this.add(sizeLabel); this.add(textLabel); this.addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { sizeLabel.setText( "Before: " + myUI.before + " after: " + myUI.after); } }); } private static class MyLabelUI extends BasicLabelUI { int before, after; @Override protected String layoutCL( JLabel label, FontMetrics fontMetrics, String text, Icon icon, Rectangle viewR, Rectangle iconR, Rectangle textR) { before = text.length(); String s = super.layoutCL( label, fontMetrics, text, icon, viewR, iconR, textR); after = s.length(); System.out.println(s); return s; } } private void display() { JFrame f = new JFrame("LayoutTest"); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.add(this); f.pack(); f.setLocationRelativeTo(null); f.setVisible(true); } public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { new LayoutTest().display(); } }); } } 
+25
Aug 30 2018-10-10T00:
source share

If you are talking about a website, i.e. output HTML / JS / CSS, you can throw away all these solutions because there is a pure CSS solution.

 text-overflow:ellipsis; 

It's not as simple as adding this style to your CSS, because it draws in with other CSS; for example, an element is required to have overflow: hidden; and if you want your text to be on the same line, white-space:nowrap; good too.

I have a stylesheet that looks like this:

 .myelement { word-wrap:normal; white-space:nowrap; overflow:hidden; -o-text-overflow:ellipsis; text-overflow:ellipsis; width: 120px; } 

You may even have a “read more” button that simply runs the javascript function to change styles and bingo, the field will change and the full text will be visible. (in my case, however, I tend to use the html header attribute for the full text, unless it is very long)

Hope this helps. This is a much simpler solution that tries to randomly calculate the size of the text and truncate it, and all that. (of course, if you are writing an application other than a website, you still need to do this)

There is one side to this solution: Firefox does not support the ellipsis style. Annoying, but I don’t think it is important. It trims the text correctly anyway, as this happens by overflowing: hidden, it just doesn't display the ellipsis. It works in all other browsers (including IE, up to IE5.5!), So it’s a little annoying that Firefox doesn’t do this yet. Hopefully the new version of Firefox will solve this problem soon.

[EDIT]
People still vote for this answer, so I have to edit it, noting that Firefox now supports the ellipsis style. This feature was added in Firefox 7. If you are using an earlier version (FF3.6 and FF4 still have some users), you are out of luck, but most FF users are now fine. Here's a lot more details about this: text-overflow: ellipsis in Firefox 4? (and FF5)

+10
Sep 07 '10 at 10:09
source share

That would be perfect for me -

  public static String ellipsis(final String text, int length) { return text.substring(0, length - 3) + "..."; } 

I would not worry about the size of each character, if I really do not know where and in what font it will be displayed. Many fonts are fixed-width fonts, each of which is the same size.

Even if it is a font of variable width, and if you count 'i', 'l' to take half the width, then why not count the 'w' 'm' to take a double width? The combination of such characters in a string usually averages the effect of their size, and I would prefer to ignore such details. Choosing wisdom wisdom would be the most important.

+4
02 Sep '10 at 5:11
source share
  public static String getTruncated(String str, int maxSize){ int limit = maxSize - 3; return (str.length() > maxSize) ? str.substring(0, limit) + "..." : str; } 
+4
May 31 '12 at 12:48
source share

How about this (to get a 50 character string):

 text.replaceAll("(?<=^.{47}).*$", "..."); 
+4
Mar 26 '15 at 19:07
source share

If you are worried that the ellipsis is hiding a very small number of characters, why not just check this condition?

 public static String ellipsis(final String text, int length) { // The letters [iIl1] are slim enough to only count as half a character. length += Math.ceil(text.replaceAll("[^iIl]", "").length() / 2.0d); if (text.length() > length + 20) { return text.substring(0, length - 3) + "..."; } return text; } 
+3
Sep 02 '10 at 4:52
source share

I would go with something similar to the standard model that you have. I wouldn’t worry about the characters, as @Gopi said this was probably the end of the balance. What I'm doing is new, has another parameter called "minNumberOfhiddenCharacters" (maybe a little less verbose). Then, when you set the ellipsis, I will do something like:

 if (text.length() > length+minNumberOfhiddenCharacters) { return text.substring(0, length - 3) + "..."; } 

What this will mean is that if the length of your text is 35, your length will be 30, and the minimum number of characters to hide is 10, then you will get a full line. If your minimum character number to hide is 3, you should get an ellipsis instead of these three characters.

The main thing you need to know is that I distorted the value "length" so that it is no longer the maximum length. The length of the output string can now be from 30 characters (if the text length> 40) to 40 characters (if the text length is 40 characters). Effectively our maximum length becomes length + minNumberOfhiddenCharacters. A string, of course, can be shorter than 30 characters if the original string is less than 30, but this is a boring case that we should ignore.

If you want the length to be stiff and fast, then you need something more:

 if (text.length() > length) { if (text.length() - length < minNumberOfhiddenCharacters-3) { return text.substring(0, text.length() - minNumberOfhiddenCharacters) + "..."; } else { return text.substring(0, length - 3) + "..."; } } 

So, in this example, if text.length () is 37, the length is 30, and minNumberOfhiddenCharacters = 10, then we will go into the second part of the internal if and get 27 characters + ... to make 30. This is actually the same as if we entered the first part of the cycle (which is a sign, we have our boundary conditions on the right). If the text length was 36, we would get 26 characters + ellipsis, giving us 29 characters with 10 hidden.

I discussed whether the restructuring of some comparison logic could make it more intuitive, but in the end I decided to leave it the way it is. You may find that text.length() - minNumberOfhiddenCharacters < length-3 makes what you do more obvious though.

+3
Sep 02 2018-10-02T00:
source share

In my eyes, you cannot get good results without the math of pixels.

Therefore, Java is probably the wrong solution to fix this problem when you are in the context of a web application (e.g. facebook).

I would go for javascript. Since Javascript is not my main area of ​​interest, I cannot judge if this is a good solution, but it can give you a pointer.

+3
03 Sep '10 at 15:30
source share

Using Guava method com.google.common.base.Ascii.truncate (CharSequence, int, String) :

 Ascii.truncate("foobar", 7, "..."); // returns "foobar" Ascii.truncate("foobar", 5, "..."); // returns "fo..." 
+1
Feb 22 '17 at 6:34
source share



All Articles