views:

200

answers:

3

This is a pretty specific problem, but I'm wondering if anyone would have a brilliant solution. I'm trying to fit a string with a fixed font size in a box of arbitrary size, such that, if the whole string doesn't fit, I want to trim it and ad an ellipses (...).

So if my text is "the netherlands" I want to determine what portion of that fits in my box of arbitrary size to look something like "the nether..."

I'm using jruby, so java or ruby is fine. I can't come up with a fantastic solution, other than trimming/testing char-by-char to see if the string fits. Since it's not a fixed width font, I can't just take each char as the same width, which would make this much easier.

Any thoughts, or tips that might point me in the right direction?

A: 

take a look at java's FontMetrics class, and in particular the stringWidth method.

Martin DeMello
I'm already using this to determine if the string fits inside the box, but that doesn't really help me algorithmically to trim the string down to a subset of characters if the string doesn't fit. Currently I'm looping char-by-char and testing the stringWidth. Seems like too much work
brad
well, you could bisect rather than looping character by character, but really, it sounds like you have to do some amount of that sort of thing to truncate properly. if you want a good starting estimate, treat everything as fixed width (via getMaxAdvance) and see where that gets you. or use getWidths and do the calculations yourself in pure code without further reference to the font.
Martin DeMello
+1  A: 

An alternate approach would be to handle the truncation of the display in CSS!

<div style="border: 1px solid gray; width: 200px;">
<span style="float: right; color: gray">...</span>
<div style="width: 184px; overflow: hidden; white-space: nowrap">
This is a really long string that goes on and on and on and we need to do something about it eventually but for now it just isn't important so I don't want to think about it.
</div></div>

This gets you out of the thankless task of worrying about your users' browsers' font metrics (which you don't know, and to which your Java server's font metrics thus bear an unknown relationship).

glenn mcdonald
that's a fair suggestion, but I'm generating an image that's being placed on a map, so it's a dynamic thing, I need the text to be in the image, as the server just returns an image based on the link params.
brad
+2  A: 

The character-by-character loop is probably just fine. But if you're doing billions of them and need to speed them up, you could binary-search for the right length instead of just iterating through the characters. Here's a generic binary_search method you can call on an Integer. Give it a <=> comparison block that returns -x 0 or +x, and it iteratively jumps by half as much each time until either the test returns 0 or the jump size gets too small.

class Integer
  def binary_search
    current = self
    nextjump = current / 2.0
    until (test = yield(current)) == 0 || nextjump < 0.5
      if test < 0
    current += nextjump
      else
        current -= nextjump
      end
      nextjump /= 2.0
    end
    current
  end
end

Then use it like this:

goal = whatever
a[0...(a.size.binary_search {|i| a[0...i].stringWidth - goal})]
glenn mcdonald
that's a good idea, I think for now I'm sticking with the char-by-char since I'm not doing billions, but I'll play around with speed if I happen to notice any performance hits. Thx!
brad