On 12/20/2016 02:11 AM, Dmitry Batrak wrote:
Missing floating-point-precision method in font measuring API
Hello,
It looks like there's currently a gap in text measuring API with
respect to
methods working with floating point coordinates. It's not something
new, but
with HiDPI support added in Java 9, it seems to become more significant.
Consider the following simplified use case of custom text component which
supports highlighting. Suppose we initially draw a couple of characters
(in paintComponent method) like this
char[] text = new char[] {'a', 'b'};
int x = ..., y = ...;
g.drawChars(text, 0, 2, x, y);
But then we need draw the second character in a different color, and
we'd like
the glyph positions to be the same. We assume the text is simple (e.g.
Latin,
without ligatures, etc). The simplest way to do it is
g.drawChars(text, 0, 1, x, y);
g.setColor(...);
int advance = g.getFontMetrics().charWidth(text[0]);
g.drawChars(text, 1, 1, x + advance, y);
Even though Swing has been doing this, we've frowned on this for a long time
because there are many rendering attributes that might affect this and it
pre-supposes an unscaled graphics. You don't really need a hidpi device,
you just need a transform for that logic to become a problem.
And that also supposes that you *know* the user didn't specify any
non-obvious
attributes on the font. There is even a "hasLayoutAttributes()" method that
should be called before you even attempt such logic.
And you still need to know its not complex text.
This will not work though, if advance is not integer. We wouldn't be
affected
by this issue, if we didn't use fractional metrics, but in HiDPI case
the advance can fractional even if fractional metrics are not used -
glyph is positioned at integer coordinates in device space, so e.g. with
200% scale, in user space it can contain half-pixels. Current solution is
float advance = (float)g.getFontMetrics().getStringBounds(text, 0, 1,
g).x;
Is that actual code, or just trying to give an idea of what you are doing.
"x" is (a) only available if you know you can cast to Rectangle, and I
don't think you can
and (b) x would give you the logical position of the char at index 0 ..
which should
always be 0.0 .. not the advance from there to the char at index 1.
I also don't understand why you aren't using the APIs on Font directly
if efficiency is a concern but as you note here ...
g.drawString(new String(text, 1, 1), x + advance, (float)y);
Alternatively, we can use font.createGlyphVector() instead of
getStringBounds
(this is done internally anyway), but in any case an instance of
GlyphVector
will be created in the process, which seems unnecessary
... we are creating a GlyphVector which is a lot more heavyweight than a
Rectangle
so you won't see a lot of savings.
- all we need is advance
which is already available, we just cannot get it directly without it
being
rounded.
You can definitely get it using getStringBounds() as you noted.
You mean you cannot get it without the extra overhead ?
Creation of new String instance, copying underlying text, also doesn't
seem to be necessary, but there's no other method to draw part of
existing text
using floating point coordinates.
Do you think adding corresponding new floating-point-based method to
FontMetrics
(and maybe to Graphics2D) makes sense? Maybe it's planned already as
part of
HiDPI-related activity? Should I raise an RFE via bugs.java.com
<http://bugs.java.com> for this?
Java 2D in JDK 1.2 added the floating point APIs to Font
I am not aware of anything you might need to do at an API level that
isn't there.
The question seems to be more about a more lightweight implementation.
What I think you are asking for is basically
public float java.awt.Font.getStringAdvance(String s, FontRenderContext frc)
and/or
public float java.awt.Font.getStringAdvance(char[] chs, int begin, int
limit, FontRenderContext frc)
Although if we accept that the Rectangle is just a minor problem, then
perhaps updating the implementation of getStringBound to avoid the
GlyphVector
in the same cases that we'd be able to do with a proposed getStringAdvance.
That would get you 95% of the available performance gain with no need to
change code.
And it would avoid needing to create new API after JDK 9 feature freeze
which
was reached this week.
I think that is something that would be expected to be done if we added
a new API anyway.
-phil.
Thanks,
Dmitry Batrak