29

Scenario

I want to use Glyphs on WP7 to create a line of text that is justified, i.e. touches the left and right border of the surrounding rectangle.

My solution

var glyphs = new Glyphs();
glyphs.FontUri = new Uri("/MyAssembly;component/MyPath/MyFont.ttf", UriKind.Relative);
glyphs.FontRenderingEmSize = 20;
glyphs.Fill = new SolidColorBrush(Colors.Red);

// measue width of space
glyphs.UnicodeString = " ";
glyphs.Measure(availableSize);
double spaceWidth = glyphs.DesiredSize.Width;
glyphs.InvalidateMeasure();

// setup justified text
string text = "Lorem Ipsum is dummy text of the printing and typesetting industry.";
int spaceCount = 10; // number of spaces in above text

glyphs.UnicodeString = text;
glyphs.Measure(availableSize); // now DesiredSize.Width = width of left aligned text

// I suspect my error to be in this formula:
double spaceAdvance = ((availableSize.Width - glyphs.DesiredSize.Width) 
                       / spaceCount + spaceWidth) / glyphs.FontRenderingEmSize * 100;
string spaceAdvanceString = String.Format(",{0};", spaceAdvance);

var indices = new StringBuilder();
foreach (char c in text)
{
    if (c == ' ')   indices.Append(spaceAdvanceString);
    else            indices.Append(';');
}
glyphs.Indices = indices.ToString();

Problem and Question

The right side of the glyphs is not exactly touching the availableSize.Width-Border but is some pixels off, and that looks weired when there are several lines of text stacked up.

What is wrong with my calculation?

Bill the Lizard
  • 398,270
  • 210
  • 566
  • 880
Tilo
  • 3,255
  • 26
  • 31
  • 2
    Have you verified that `(spaceCount * spaceWidth) + (text.Replace(" ", "") == (text)`? The little I know about typesetting is that you apply all sorts of tricks to make it appear even and possibly also pixel aligned where possible. – Albin Sunnanbo Mar 10 '11 at 20:20
  • @Albin: No, it is not equal! Unfortunately this only seems to account for part of the "error", but it already helped me. – Tilo Mar 10 '11 at 22:47

3 Answers3

2

This could be down to problems with floating point accuracy.

Generally speaking, every floating-point arithmetic operation introduces an error at least equal to the machine accuracy (i.e. the smallest number that, when added to 1.0, produces a floating-point result that is different from 1.0) into the result. This error is known as roundoff error. Roundoff errors are cumulative and sometimes dependent on the order of operations.

Instead of

double spaceAdvance = ((availableSize.Width - glyphs.DesiredSize.Width)
         / spaceCount + spaceWidth) / glyphs.FontRenderingEmSize * 100;

try moving the multiplication to the front, i.e.

double spaceAdvance = 100.0 * ((availableSize.Width - glyphs.DesiredSize.Width)
         / spaceCount + spaceWidth) / glyphs.FontRenderingEmSize;

Alternatively, you could also try

double spaceAdvance = (100.0 * (availableSize.Width - glyphs.DesiredSize.Width)
         / spaceCount + 100.0 * spaceWidth) / glyphs.FontRenderingEmSize;
Chris Bednarski
  • 3,364
  • 25
  • 33
  • Very good point, but unfortunately, this seems to have no impact on the visual appearance. I did some experiments with the order of operations and found that, for typical numbers in this scenario, the result is equal in the first twelve decimal digits, which means that the round-off error accounts for one millionth of a pixel or less - depending on the font rendering em size. – Tilo Jun 17 '11 at 17:23
1

Alternative proposal: You can use the RichTextBox control which supports "justify".

Jasper
  • 660
  • 1
  • 7
  • 19
  • The RichTextBox was not available when I asked the question, but it is now probably a good workaround. In the mean time I learnt how to make the Glyphs behave the way I want. Everyone, please contact me if you need details. The biggest help was the comment from Albin. Chris had a really good "feel" for where to search the problem and I suggest everyone who looks at this question reads his answers carefully, but unfortunatley he missed the real problem in my special case. – Tilo May 25 '12 at 15:04
1

What about layout rounding?

var glyphs = new Glyphs();
glyphs.FontUri = new Uri("/MyAssembly;component/MyPath/MyFont.ttf", UriKind.Relative);
glyphs.FontRenderingEmSize = 20;
glyphs.Fill = new SolidColorBrush(Colors.Red);
glyphs.UseLayoutRounding = false;

Also, you may want to experiment with ActualWidth instead of DesiredSize.Width

double spaceAdvance = ((availableSize.Width - glyphs.ActualWidth) / 
             spaceCount + spaceWidth) / glyphs.FontRenderingEmSize * 100.0;
Chris Bednarski
  • 3,364
  • 25
  • 33