Nice programing

줄임표가있는 문자열을 자르는 이상적인 방법

nicepro 2020. 12. 27. 20:48
반응형

줄임표가있는 문자열을 자르는 이상적인 방법


저는 우리 모두가 Facebook 상태 (또는 다른 곳)에서 줄임표 '를 보았고 "더보기"를 클릭했으며 2 자 정도 밖에 남지 않았습니다. 확실히 이상적인 방법이 있기 때문에 이것이 게으른 프로그래밍 때문이라고 생각합니다.

Mine은 날씬한 문자 [iIl1]를 "반 문자"로 계산 하지만, 거의 모든 문자를 숨길 때 어리석은 줄임표가 나타나지 않습니다.

이상적인 방법이 있습니까? 다음은 내 것입니다.

/**
 * 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;
}

언어는별로 중요하지 않지만 Java로 태그가 지정되었습니다. 그 이유는 제가 주로보고있는 것이기 때문입니다.


나는 "얇은"문자를 반 문자로 간주하는 아이디어를 좋아합니다. 간단하고 좋은 근사치.

그러나 대부분의 줄임표의 주요 문제 는 중간에 단어를 자르는 (imho)입니다 . 다음은 단어 경계를 고려한 솔루션입니다 (그러나 pixel-math 및 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) + "...";
}

알고리즘 테스트는 다음과 같습니다.

여기에 이미지 설명 입력


아무도 Commons Lang StringUtils # abbreviate ()를 언급하지 않았다는 사실에 놀랐습니다 .

업데이트 : 예, 슬림 한 문자를 고려하지 않지만 모든 사람이 다른 화면과 글꼴 설정을 가지고 있으며이 페이지에있는 많은 사람들이 아마도 다음과 같은 유지 관리 라이브러리를 찾고 있다는 점에 동의하지 않습니다. 위.


Java 그래픽 컨텍스트에서 더 정확한 지오메트리를 얻을 수있는 것 같습니다 FontMetrics.

부록 :이 문제에 접근 할 때 모델과 뷰를 구별하는 것이 도움이 될 수 있습니다. 모델은 StringUTF-16 코드 포인트의 유한 시퀀스 인 반면 뷰는 일부 장치에서 일부 글꼴로 렌더링 된 일련의 글리프입니다.

Java의 특정 경우 SwingUtilities.layoutCompoundLabel()에는 번역에 영향을 미치는 데 사용할 수 있습니다 . 아래 예제 BasicLabelUI는 효과를 보여주기 위해 레이아웃 호출을 가로 챕니다 . 다른 상황에서 효용 방법을 사용하는 것이 가능할 수 있지만 적절한 방법 FontMetrics은 경험적으로 결정되어야합니다.

대체 텍스트

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();
            }
        });
    }
}

HTML / JS / CSS 출력과 같이 웹 사이트에 대해 이야기하고 있다면 순수한 CSS 솔루션이 있기 때문에 이러한 모든 솔루션을 버릴 수 있습니다.

text-overflow:ellipsis;

CSS에 해당 스타일을 추가하는 것만 큼 간단하지는 않습니다. 다른 CSS와 상호 작용하기 때문입니다. 예를 들어 요소에 overflow : hidden이 있어야합니다. 한 줄로 된 텍스트를 원하는 경우 white-space:nowrap;에도 좋습니다.

다음과 같은 스타일 시트가 있습니다.

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

스타일을 변경하기 위해 자바 스크립트 기능을 실행하는 "더 읽기"버튼을 사용할 수도 있습니다. 빙고, 상자 크기가 조정되고 전체 텍스트가 표시됩니다. (내 경우에는 매우 길어지지 않는 한 전체 텍스트에 html 제목 속성을 사용하는 경향이 있습니다)

도움이되기를 바랍니다. 텍스트 크기를 계산하고 자르는 등의 모든 것을 엉망으로 만들려는 훨씬 더 간단한 솔루션입니다. (물론 웹 기반이 아닌 앱을 작성하는 경우에도 그렇게해야 할 수 있습니다.)

There is one down-side to this solution: Firefox doesn't support the ellipsis style. Annoying, but I don't think critical -- It does still truncate the text correctly, as that is dealt with by by overflow:hidden, it just doesn't display the ellipsis. It does work in all the other browsers (including IE, all the way back to IE5.5!), so it's a bit annoying that Firefox doesn't do it yet. Hopefully a new version of Firefox will solve this issue soon.

[EDIT]
People are still voting on this answer, so I should edit it to note that Firefox does now support the ellipsis style. The feature was added in Firefox 7. If you're using an earlier version (FF3.6 and FF4 still have some users) then you're out of luck, but most FF users are now okay. There's a lot more detail about this here: text-overflow:ellipsis in Firefox 4? (and FF5)


For me this would be ideal -

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

I would not worry about the size of every character unless I really know where and in what font it is going to be displayed. Many fonts are fixed width fonts where every character has same dimension.

Even if its a variable width font, and if you count 'i', 'l' to take half the width, then why not count 'w' 'm' to take double the width? A mix of such characters in a string will generally average out the effect of their size, and I would prefer ignoring such details. Choosing the value of 'length' wisely would matter the most.


How about this (to get a string of 50 chars):

text.replaceAll("(?<=^.{47}).*$", "...");

 public static String getTruncated(String str, int maxSize){
    int limit = maxSize - 3;
    return (str.length() > maxSize) ? str.substring(0, limit) + "..." : str;
 }

If you're worried about the ellipsis only hiding a very small number of characters, why not just check for that 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;
}

I'd go with something similar to the standard model that you have. I wouldn't bother with the character widths thing - as @Gopi said it is probably goign to all balance out in the end. What I'd do that is new is have another paramter called something like "minNumberOfhiddenCharacters" (maybe a bit less verbose). Then when doign the ellipsis check I'd do something like:

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

What this will mean is that if your text length is 35, your "length" is 30 and your min number of characters to hide is 10 then you would get your string in full. If your min number of character to hide was 3 then you would get the ellipsis instead of those three characters.

The main thing to be aware of is that I've subverted the meaning of "length" so that it is no longer a maximum length. The length of the outputted string can now be anything from 30 characters (when the text length is >40) to 40 characters (when the text length is 40 characters long). Effectively our max length becomes length+minNumberOfhiddenCharacters. The string could of course be shorter than 30 characters when the original string is less than 30 but this is a boring case that we should ignore.

If you want length to be a hard and fast maximum then you'd want something more like:

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, length is 30 and minNumberOfhiddenCharacters = 10 then we'll go into the second part of the inner if and get 27 characters + ... to make 30. This is actually the same as if we'd gone into the first part of the loop (which is a sign we have our boundary conditions right). If the text length was 36 we'd get 26 characters + the ellipsis giving us 29 characters with 10 hidden.

I was debating whether rearranging some of the comparison logic would make it more intuitive but in the end decided to leave it as it is. You might find that text.length() - minNumberOfhiddenCharacters < length-3 makes it more obvious what you are doing though.


In my eyes, you can't get good results without pixel math.

Thus, Java is probably the wrong end to fix this problem when you are in a web application context (like facebook).

나는 자바 스크립트에 갈 것입니다. Javascript는 내 주요 관심 분야가 아니기 때문에 이것이 좋은 해결책 인지 판단 할 수는 없지만 포인터를 제공 할 수 있습니다.


Guava의 com.google.common.base.Ascii.truncate (CharSequence, int, String) 메서드 사용 :

Ascii.truncate("foobar", 7, "..."); // returns "foobar"
Ascii.truncate("foobar", 5, "..."); // returns "fo..."

이 솔루션의 대부분은 글꼴 메트릭을 고려하지 않습니다. 여기 몇 년 동안 사용해온 자바 스윙에 대한 매우 간단하지만 작동하는 솔루션이 있습니다.

private String ellipsisText(String text, FontMetrics metrics, Graphics2D g2, int targetWidth) {
   String shortText = text;
   int activeIndex = text.length() - 1;

   Rectangle2D textBounds = metrics.getStringBounds(shortText, g2);
   while (textBounds.getWidth() > targetWidth) {
      shortText = text.substring(0, activeIndex--);
      textBounds = metrics.getStringBounds(shortText + "...", g2);
   }
   return activeIndex != text.length() - 1 ? shortText + "..." : text;
}

참조 URL : https://stackoverflow.com/questions/3597550/ideal-method-to-truncate-a-string-with-ellipsis

반응형