5

I want to compute the exact text width in metric units of a given string. My pseudocode looks like this:

Bitmap.Canvas.Assign(Font);
PixelWidth := Bitmap.Canvas.TextWidth(Font)
MetricWidth := PtToMM * (PixelWidth * 72.0 / GetScreenDPI);

PtToMM is a constant that is defined as 0.352777778. This is pretty accurate for some fonts and font sizes but for others it is either too small or too large. I experimented a lot witht other possiblities such as GetCharWidth32 and GetCharABCWidths, also with mapping mode MM_LOMETRIC but I just can't get it to work. This problem is haunting me, so please can anyone help and show me where I'm wrong. Thank you very much!

EDIT I checked for one string: metric width is computed as 4.17 cm, the width on the actual printout (measured on paper) is 4.4 cm (font Times New Roman, size 12).

jpfollenius
  • 16,456
  • 10
  • 90
  • 156
  • 2
    Some questions: How do you know the computed text width is not what it should be? Are you taking the text width using the correct DC? (ie: any chance you're taking the reading on a screen canvas and then using the result on a print canvas?). I've never used this kind of calculation in order to obtain actual METRIC values, but the PIXEL values have always been accurate. I've used similar code to print 1 meter long tables using dot matrix printers on continuous paper, where the width of columns was calculated based on text content. – Cosmin Prund Feb 17 '12 at 11:14
  • @Comsin: not sure I get your question. I'm using this for a print preview and for printing, so it's used both on screen and on the printer. – jpfollenius Feb 17 '12 at 11:27
  • 1
    This sounds like you're trying to be smarter than Windows. You shouldn't be calculating this yourself, you should be letting your printer device context tell YOU what the width is on paper? – Warren P Feb 17 '12 at 14:11
  • Pixels are logical units, so **metric** term should be replaced with **physical**. I did some similar experiments couple of years ago, found out what display devices are never accurate. – OnTheFly Feb 17 '12 at 17:49
  • What @CosminPrund is asking is if you're trying to calculate theses sizes using the screen's DC vs. the printer's DC. If you're using the screen's DC, it's probably not high enough resolution to ensure very accurate physical measurements. Also, the printer may be substituting its own version of Times with slightly different metrics. Finally, your calculation appears to go from pixels -> inches -> points -> millimeters. That's a lot of places to accumulate error. – afrazier Feb 17 '12 at 20:04

1 Answers1

0

I didn't test it extensively, but it seems to be giving the proper results. The result is the width of the text in thousandth of milimeter. This function doesn't support wordwrapping and other special considerations.

Also, note that when working with printer, improper Print Registration can "stretch" the text.

Also, note that for printer, changing, for example, the print resolution from 300 ppp to 1200 ppp will also change the result.

uses
  ConvUtils, stdConvs ;

function CalcRequiredTextWidth(aDC : HDC; aFont : TFont; const asText: string): Double;
var vCanvas : TCanvas;
    iPixelsWidth : Integer;
    dInchWidth : Double;
    iFontSize : Integer;
begin
  vCanvas := TCanvas.Create;
  try
    vCanvas.Handle := aDC;

    vCanvas.Font.Assign(aFont);
    iFontSize := vCanvas.Font.Size;
    vCanvas.Font.PixelsPerInch := GetDeviceCaps(aDC, LOGPIXELSY);
    vCanvas.Font.Size := iFontSize;

    iPixelsWidth := vCanvas.TextExtent(asText).cx;

    dInchWidth := iPixelsWidth / GetDeviceCaps(vCanvas.Handle, LOGPIXELSX);

    Result := Convert(dInchWidth, duInches, duMicrons);

  finally
    vCanvas.Free;
  end;
end;
Ken Bourassa
  • 6,363
  • 1
  • 19
  • 28