1

Summarization:

No luck in finding how to output the angstrom sign using AggPas library.

===============================================

The text-drawing function in the AggPas library takes a PAnsiChar parameter. I am wondering how can I can use PAnsiChar to point to text containing angstrom (Å)?

  SetThreadLocale($0409);                                       // No difference.

  ShowMessage(Chr(0197));                                       // Correct
  ShowMessage(AnsiString(Chr(0197)));                           // Wrong - question mark
  ShowMessage(PAnsiChar(Chr(0197)));                            // AV
  ShowMessage(UTF8String(Chr(0197)));                           // Correct
  ShowMessage(UTF8Encode(Chr(0197)));                           // Correct
  ShowMessage(RawByteString(Chr(0197)));                        // Wrong - question mark

  ShowMessage(AnsiString(UTF8String(Chr(0197))));               // Wrong - question mark
  ShowMessage(AnsiString(UTF8Encode(Chr(0197))));               // Correct

  ShowMessage(RawByteString(UTF8String(Chr(0197))));            // Wrong - question mark
  ShowMessage(RawByteString(UTF8Encode(Chr(0197))));            // Correct

  ShowMessage(PAnsiChar(AnsiString(UTF8Encode(Chr(0197)))));    // Wrong - strange character
  ShowMessage(PAnsiChar(RawByteString(UTF8Encode(Chr(0197))))); // Wrong - strange character

For your convenience, the DrawTextCenterAligned procedure in the following code cannot output the angstrom letter.

    unit u2DRenderEngine_aggpas;

    interface

    uses
      u2DRenderEngine, uMathVector3D,
      agg_2D,
      Graphics, IniFiles, Types;

    type
      T2DRenderEngine_aggpas = class;

      T2DRenderEngine_aggpas = class(T2DRenderEngine)

      private
        fFontBMP: TBitmap;
        fVG: Agg2D;

      protected

        function GetActualStringBoundingBox(aText: string; aFont: TFont)
          : TRect; override;

      public

        constructor Create;
        destructor Destroy; override;

        procedure AttachBMP(aBMP: TBitmap; flip_y: Boolean);

        procedure Flush; override;

        procedure DrawLine(aP, bP: TPoint3D; aPen: TPen); override;
        procedure DrawCircle(Center: TPoint3D; Radius: Extended;
          R, G, B: Integer); override;
        procedure FillCircle(Center: TPoint3D; Radius: Extended;
          R, G, B: Integer); override;
        procedure DrawPolygon(aPts: TAPoint3D; R, G, B: Integer); override;
        procedure FillPolygon(aPts: TAPoint3D; R, G, B: Integer); override;

        procedure DrawTextLeftAligned(aLeft: TPoint3D; aText: string; aFont: TFont;
          clearBackground: Boolean); override;
        procedure DrawTextCenterAligned(aCenter: TPoint3D; aText: string;
          aFont: TFont; clearBackground: Boolean); override;

      end;

    implementation

    uses
      u2DUtils_Vcl, SysUtils, Math;

    { TRenderEngine_2D_aggpas }

    constructor T2DRenderEngine_aggpas.Create;
    begin
      inherited;

      fFontBMP := TBitmap.Create;
      fFontBMP.Width := 2;
      fFontBMP.Height := 2;

      fVG.Construct;
    end;

    destructor T2DRenderEngine_aggpas.Destroy;
    begin

      inherited;
    end;

    procedure T2DRenderEngine_aggpas.AttachBMP(aBMP: TBitmap; flip_y: Boolean);
    var
      tmpBuffer: pointer;
      tmpStride: Integer;
    begin
      if aBMP.Empty then
        raise Exception.Create('AttachBMP: aBMP is Empty!');

      if aBMP.PixelFormat <> pf32bit then
        raise Exception.Create('AttachBMP: aBMP should be 32bit!');

      tmpStride := Integer(aBMP.ScanLine[1]) - Integer(aBMP.ScanLine[0]);

      if tmpStride < 0 then
        tmpBuffer := aBMP.ScanLine[aBMP.Height - 1]
      else
        tmpBuffer := aBMP.ScanLine[0];

      if flip_y then
        tmpStride := tmpStride * -1;

      fVG.attach(tmpBuffer, aBMP.Width, aBMP.Height, tmpStride);
    end;

    procedure T2DRenderEngine_aggpas.Flush;
    begin
    end;

    procedure T2DRenderEngine_aggpas.DrawLine(aP, bP: TPoint3D; aPen: TPen);
    begin
      fVG.line(aP.X, aP.Y, bP.X, bP.Y);
    end;

    procedure T2DRenderEngine_aggpas.DrawCircle(Center: TPoint3D; Radius: Extended;
      R, G, B: Integer);
    begin
      fVG.lineColor(R, G, B);
      fVG.noFill;
      fVG.ellipse(Center.X, Center.Y, Radius, Radius);
    end;

    procedure T2DRenderEngine_aggpas.FillCircle(Center: TPoint3D; Radius: Extended;
      R, G, B: Integer);
    begin
      fVG.fillColor(R, G, B);
      fVG.noLine;
      fVG.ellipse(Center.X, Center.Y, Radius, Radius);
    end;

    procedure T2DRenderEngine_aggpas.DrawPolygon(aPts: TAPoint3D; R, G, B: Integer);
    var
      Len, I: Integer;
      poly: array of double;
    begin
      Len := Length(aPts);

      SetLength(poly, Len * 2);
      for I := 0 to Len - 1 do
      begin
        poly[2 * I] := aPts[I].X;
        poly[2 * I + 1] := aPts[I].Y;
      end;

      fVG.lineColor(R, G, B);
      fVG.noFill;
      fVG.polygon(@poly[0], 4);
    end;

    procedure T2DRenderEngine_aggpas.FillPolygon(aPts: TAPoint3D; R, G, B: Integer);
    var
      Len, I: Integer;
      poly: array of double;
    begin
      Len := Length(aPts);

      SetLength(poly, Len * 2);
      for I := 0 to Len - 1 do
      begin
        poly[2 * I] := aPts[I].X;
        poly[2 * I + 1] := aPts[I].Y;
      end;

      fVG.fillColor(R, G, B);
      fVG.noLine;
      fVG.polygon(@poly[0], 4);
    end;

    procedure T2DRenderEngine_aggpas.DrawTextLeftAligned(aLeft: TPoint3D;
      aText: string; aFont: TFont; clearBackground: Boolean);
    var
      tmpRect: TRect;
      tmpRectWidth, tmpRectHeight: Integer;
      tmpPt: TPoint3D;
    begin
      tmpRect := GetActualStringBoundingBox(aText, aFont);
      tmpRectWidth := tmpRect.Right - tmpRect.Left;
      tmpRectHeight := tmpRect.Bottom - tmpRect.Top;
      tmpPt.X := aLeft.X;
      tmpPt.Y := aLeft.Y - tmpRectHeight;

      if clearBackground then
      begin
        fVG.fillColor(255, 255, 255);
        fVG.noLine;
        fVG.Rectangle(tmpPt.X, tmpPt.Y, tmpPt.X + tmpRectWidth,
          tmpPt.Y + tmpRectHeight);
      end;

      // Font & Colors
      fVG.fillColor(0, 0, 0);
      fVG.noLine;
      fVG.TextHints(True);
      if Agg2DUsesFreeType then
        fVG.Font(PAnsiChar(AnsiString(UTF8Encode(LowerCase(aFont.Name) + '.ttf'))),
          Abs(aFont.Height))
      else
        fVG.Font('Arial', 40.0);
      // Text
      fVG.Text(tmpPt.X, tmpPt.Y + tmpRectHeight, PAnsiChar(AnsiString(aText)));
    end;

    procedure T2DRenderEngine_aggpas.DrawTextCenterAligned(aCenter: TPoint3D;
      aText: string; aFont: TFont; clearBackground: Boolean);
    var
      tmpRect: TRect;
      tmpRectWidth, tmpRectHeight: Integer;
      tmpPt: TPoint3D;
    begin
      tmpRect := GetActualStringBoundingBox(aText, aFont);
      tmpRectWidth := tmpRect.Right - tmpRect.Left;
      tmpRectHeight := tmpRect.Bottom - tmpRect.Top;
      tmpPt.X := aCenter.X - tmpRectWidth / 2.0;
      tmpPt.Y := aCenter.Y - tmpRectHeight / 2.0;

      if clearBackground then
      begin
        fVG.fillColor(255, 255, 255);
        fVG.noLine;
        fVG.Rectangle(tmpPt.X, tmpPt.Y, tmpPt.X + tmpRectWidth,
          tmpPt.Y + tmpRectHeight);
      end;

      // Font & Colors
      fVG.fillColor(0, 0, 0);
      fVG.noLine;
      fVG.TextHints(True);
      if Agg2DUsesFreeType then
        fVG.Font(PAnsiChar(AnsiString(UTF8Encode(LowerCase(aFont.Name) + '.ttf'))),
          Abs(aFont.Height))
      else
        fVG.Font('Arial', 40.0);
      // Text
      fVG.Text(tmpPt.X, tmpPt.Y + tmpRectHeight, PAnsiChar(AnsiString(aText)));
    end;

    function T2DRenderEngine_aggpas.GetActualStringBoundingBox(aText: string;
      aFont: TFont): TRect;
    var
      tmpRectWidth, tmpRectHeight: Integer;
    begin
      Self.fFontBMP.Canvas.Font.Assign(aFont);
      tmpRectWidth := Self.fFontBMP.Canvas.TextWidth(aText);
      tmpRectHeight := Self.fFontBMP.Canvas.TextHeight(aText);

      // 2011-03-07 hard-coded
      tmpRectWidth := Ceil(tmpRectWidth * 1.05);
      // 2011-03-07 hard-coded
      tmpRectHeight := Ceil(tmpRectHeight * 0.70);

      FillChar(Result, SizeOf(Result), 0);
      Result.Right := tmpRectWidth;
      Result.Bottom := tmpRectHeight;
    end;

    end.
SOUser
  • 3,802
  • 5
  • 33
  • 63
  • 1
    Which Delphi version? Also, the ShowMessage is nothing to do with AggPas, is that right? – David Heffernan Mar 09 '11 at 19:52
  • 1
    What method are you trying to call in AggPas? – David Heffernan Mar 09 '11 at 19:53
  • 1
    Notice that there is a difference between the Swedish letter Å (U+00C5: LATIN CAPITAL LETTER A WITH RING ABOVE) and the Ångström sign Å (U+212B: ANGSTROM SIGN). – Andreas Rejbrand Mar 09 '11 at 20:05
  • @David: Thank you very much for your time! I tried the code in Delphi 2010. You are right. I checked ShowMessage because I wanted to see where the angstrom is lost at the first place. – SOUser Mar 09 '11 at 20:07
  • 1
    @Xichen OK, what method in AggPas? – David Heffernan Mar 09 '11 at 20:07
  • @David: My line of calling AggPas: `fVG.Text(tmpPt.X, tmpPt.Y + tmpRectHeight, PAnsiChar(AnsiString(aText)));` fVG is of Agg2D object type, and aText is of regular string type. – SOUser Mar 09 '11 at 20:07
  • @Andreas: Thank you very much for your comments! Didn't know that before you say! – SOUser Mar 09 '11 at 20:08
  • @Andreas: I guess then I should use the angstrom sign when it stands for distance unit? – SOUser Mar 09 '11 at 20:20
  • 2
    @Xiechen Li: Yes, that is the most logical thing to do. But you must be sure that the end-user has a decent Unicode font that contains the Ånström sign. All modern versions of Windows come with such fonts, but if you are targeting older systems, it might be safer to use the (much more common) Swedish letter. And it isn't too disturbing: After all, the unit is named after a Swedish physicist Ångström. And so it makes actually sense to use the Swedish letter if that's easier. (But, of course, I **hate** when people use a superscript o or masculine indicator (º) instead of the degree sign (°)!) – Andreas Rejbrand Mar 09 '11 at 20:25
  • Maybe you should find a better graphics library that isn't PAnsiChar based? – Warren P Mar 09 '11 at 21:09

2 Answers2

2

If the unit takes it's input as PAnsiChar, you're toast. Unless the default code page on your system can encode the Å character, there's simply no way of putting that information into an ANSI CHAR. And if such encoding was available, all of your routines that now show question marks would have shown the proper char.


Slightly longer info:

Unicode is encoding a vast amount of characters, including all characters in all written languages, special symbols, musical notes, the space is so vast there's encoding for Klingon characters! Ansi chars are encoded using a table lookup that maps the values that can be encoded in one byte to a selection of Unicode chars.

When using AnsiString you can only use less then 256 Unicode chars at a time. When you try to encode one Unicode char to one AnsiString you'll essentially doing a lookup in the code page table, looking for a code that points back to the original Unicode char. If no such encoding is available you get the famous question mark!


Here's a routine that converts string to UTF8 string and returns it as AnsiString (all UTF8 is actually valid - but meaningless - AnsiString):

function Utf8AsAnsiString(s:string):AnsiString;
var utf8s:UTF8String;
begin
  utf8s := UTF8String(s);
  SetLength(Result, Length(utf8s));
  if Length(utf8s) > 0 then
    Move(utf8s[1], Result[1], Length(utf8s));
end;

You can pass the result of this function and hope the unit can handle UTF8. Good luck.

Cosmin Prund
  • 25,498
  • 2
  • 60
  • 104
  • 1
    @Cosmin Not necessarily. The code could be expecting UTF-8. What's more this code compiles with a very wide variety of Delphi and FP compilers. – David Heffernan Mar 09 '11 at 19:59
  • 1
    @David, that's true, and it's probably worth a shot. – Cosmin Prund Mar 09 '11 at 20:08
  • @Cosmin: Thank you for your knowledgeable comments! I guess I could not use AggPas to output the angstrom letter then? – SOUser Mar 09 '11 at 20:13
  • 1
    I see no evidence in the AggPas source for any support for Unicode. I personally think you'd be much better off with graphics32 than AggPas which I find somewhat weird. graphics32 seems to be more active. – David Heffernan Mar 09 '11 at 20:20
  • 1
    According to this page: [Code pages that support Unicode Character 'ANGSTROM SIGN' (U+212B)](http://www.fileformat.info/info/unicode/char/212b/codepage_support.htm) no windows code page can encode that character, so true AnsiChar can't do it. As suggested by David you may try passing UTF8 and hope the unit can handle it. I'll edit the answer to include some code that does that. If that also fails then you have the option of modifying the unit yourself to make it handle UTF8 or some other Unicode encoding. – Cosmin Prund Mar 09 '11 at 20:23
  • @David: Thank you for your suggestion! I actually have another unit wrapping up the Graphics32 library (actually using IGDIPlus to draw onto TCustomPaintBox32.Buffer). Graphics32 (with IGDIPlus) also has better performance. It is just AggPas provides cross-platform possibility (Win, Linux, Mac), and as a first step, I wanted to see whether it meets my requirement. :D – SOUser Mar 09 '11 at 20:26
  • 2
    @Xichen I believe that graphics32 is very cross-platform – David Heffernan Mar 09 '11 at 20:30
  • @David: Thank you for your comment! I know Graphics32 mentions cross-platform in their webpage. However, when I tried Graphics v1.8.3 with Lazarus 0.9.28.2 last time, I met exceptions here and there. Sorry that I don't have the record now. But since you confirm it is very cross-platform, I should try with the new versions ( GR32 v1.9 & Laz v0.9.30) to see. – SOUser Mar 09 '11 at 20:36
1

You need an intermediary step to load the char into a string, like so:

const
  ANGSTROM = Chr(0197);

procedure ShowAngstrom;
var
  message: AnsiString;
begin
  message := ANGSTROM;
  ShowMessage(PAnsiChar(message));
end;

EDIT: Here's a guess as to what the problem might be if this doesn't work for AggPas.

I'm not familiar with AggPas, but I have used the Asphyre graphics library, and its text-drawing system, back in the pre-Unicode days, required you to generate a specialized bitmap by giving it a font file and a range of characters it could print. (Not sure if it's improved since then; I haven't used it in a while.) Any character outside that range wouldn't print properly.

If AggPas works in a similar way, it could be that the font image you have doesn't contain anything for character 197, so no matter how correct your Delphi code is, it's got nothing to map to and you're not going to get the right output. See if you can verify this.

If not... then I'm out of ideas and I hope someone else here is more familiar with the problem.

Mason Wheeler
  • 82,511
  • 50
  • 270
  • 477
  • 1
    That's not going to work. ShowMessageW isn't going to like that char*! Xichen is using one of the Unicode versions (at least he has in recent Qs) – David Heffernan Mar 09 '11 at 20:06
  • 1
    That's essentially equivalent to this code already tested by the OP: `ShowMessage(AnsiString(Chr(0197)));` - and that's showing a question mark for the OP. – Cosmin Prund Mar 09 '11 at 20:10
  • @Mason: Thank you for your time! The message dialog still displays a question mark. Or am I missing something? – SOUser Mar 09 '11 at 20:11
  • 1
    @Xichen: I don't have AggPas to check, but that ShowMessage code worked properly for me when I tested it in Delphi2010. It looks to me like AggPas is a 2D graphics library. See my edit for a guess as to what might be going on. – Mason Wheeler Mar 09 '11 at 20:25
  • 1
    @Mason, I don't think your guess is correct. There simply isn't a code page on windows that's capable of encoding the ANGSTROM SIGN, nothing to do with the font; It's a simple encoding problem. When you try to encode as Ansi String something that's not available, you get a question mark. When the font is missing the glyph you get an square. – Cosmin Prund Mar 09 '11 at 20:38
  • @Mason: Oh! Thank you for sharing your experience! Regarding that the ShowMessage works for you, perhaps your code page has that character as Cosmin mentioned. :D – SOUser Mar 09 '11 at 20:40
  • @Cosmin: But Mason mentioned that he did see the angstrom? – SOUser Mar 09 '11 at 20:41