How to create an IDWriteFontFile - delphi

I would like to create a Direct2D path geometry from text. As I understood, I'll need to create an IDWriteFontFace, from which I'll have to call GetGlyphRunOutline.
Unfortunately, I cannot figure out how to create that font face. So far, I stumble on even creating a font file reference, which I think I have to use to create the font face.
procedure CreateFontFace;
var
hr: HRESULT;
FontDir: string;
FontPath: string;
ft: _FILETIME;
FontFile: IDWriteFontFile;
FontFace: IDWriteFontFace;
begin
FontDir := GetSpecialFolder(CSIDL_FONTS);
FontPath := IncludeTrailingPathDelimiter(FontDir) + 'Arial.ttf';
// Here, FontPath contains 'C:\Windows\Fonts\Arial.ttf'
// (which exists on my machine)
ft.dwLowDateTime := 0;
ft.dwHighDateTime := 0;
hr := DWriteFactory.CreateFontFileReference(
FontPath, // DOES NOT COMPILE
ft,
FontFile);
if Succeeded(hr) then begin
hr := DWriteFactory.CreateFontFace(
DWRITE_FONT_FACE_TYPE_TRUETYPE,
1,
#FontFile,
0,
DWRITE_FONT_SIMULATIONS_NONE,
FontFace);
end;
end;
The prototype of CreateFontFileReference in Winapi.D2D1 is as follow:
function CreateFontFileReference(var filePath: WCHAR;
var lastWriteTime: FILETIME;
out fontFile: IDWriteFontFile): HResult; stdcall;
I understand that putting a string instead of a WCHAR can bother the compiler, but how should this be written? I'm also interested if there is another, simpler way...
UPDATE:
As stated by Remy Lebeau, there are other similar buggy declarations in the Winapi.D2D1 unit. The second one that I encountered is even in CreateFontFileReference too: parameter lastWriteTime should be a pointer, so to make my code work, I had to change my use of the ft variable as follows:
var
...
ft: ^_FILETIME;
...
begin
...
ft := nil;
hr := DWriteFactory.CreateFontFileReference(
PChar(FontPath)^,
ft^, // Yes, I am dereferencing nil, and it's working!
FontFile);
...
end;

If you are using Delphi 2009 or later, where String is Unicode, you need to typecast your String to PChar when passing it to CreateFontFileReference():
hr := DWriteFactory.CreateFontFileReference(
PChar(FontPath),
ft,
FontFile);
If you are using Delphi 2007 or earlier, where String is Ansi, you need to convert your String to a WideString first and then typecast that to PWideChar:
hr := DWriteFactory.CreateFontFileReference(
PWideChar(WideString(FontPath)),
ft,
FontFile);
Update: turns out there is a bug in the declaration of the first parameter of CreateFontFileReference(). Embarcadero declares it as var filePath: WCHAR, but it should have been declared as const filePath: PWCHAR instead. So you will have to account for that bug by dereferencing the PChar/PWideChar pointer, eg:
hr := DWriteFactory.CreateFontFileReference(
PChar(FontPath)^,
...);
hr := DWriteFactory.CreateFontFileReference(
PWideChar(WideString(FontPath))^,
...);

Related

Delphi. filewrite, error - E2036 Variable required

I didn't use filewrite a lot of time.
I made this procedure and received the next error: E2036 Variable required (on flen variable in filewrite).
procedure TForm1.WriteFN(const PIN: integer);
var
lFile: integer;
flen : integer;
begin
flen := 2;
lFile := FileOpen('/sys/pins', fmOpenWrite);
try
if filewrite(lFile, PChar(IntToStr(PIN)), flen) = -1 then
raise Exception.CreateFmt('Cannot export PIN%d', [PIN]);
finally
fileclose(lFile);
end;
end;
How to solve this problem?
Delphi Rio, Win10.
The second parameter of FileWrite() is an untyped const. Whatever you pass to it gets passed by reference, and as such you have to give it a real variable to refer to. In this case, you can simply dereference the PChar pointer, that will let the parameter reference the 1st Char in the temporary String you are creating, eg:
FileWrite(lFile, PChar(IntToStr(PIN))^, flen)
Note, however, that FileWrite() operates on raw bytes, not on string characters. You are telling FileWrite() to write exactly 2 bytes, which may or may not work properly, depending on which version of Delphi you are using, and what value the PIN contains.
Try this instead:
procedure TForm1.WriteFN(const PIN: integer);
var
lFile: integer;
flen : integer;
s: AnsiString;
begin
s := AnsiString(IntToStr(PIN));
flen := Length(s);
lFile := FileOpen('/sys/pins', fmOpenWrite);
if lFile = -1 then
raise Exception.CreateFmt('Cannot create file to export PIN%d', [PIN]);
try
if FileWrite(lFile, PAnsiChar(s)^, flen) = -1 then
raise Exception.CreateFmt('Cannot write to file to export PIN%d', [PIN]);
finally
FileClose(lFile);
end;
end;
If you are using a modern version of Delphi, consider using TFile.WriteAllText() instead:
uses
..., System.IOUtils;
procedure TForm1.WriteFN(const PIN: integer);
begin
try
TFile.WriteAllText('/sys/pins', IntToStr(PIN));
except
Exception.RaiseOuterException(
Exception.CreateFmt('Cannot export PIN%d', [PIN])
);
end;
end;

What's the correct way to assign PAnsiChar to a (unicode-) string?

I have got a DLL function that returns a pointer to ANSI text (PAnsiChar). I want to assign this to a (unicode-) string (This is Delphi XE2.). The following compiles but I get a warning
"W1057 Implicit String cast from 'AnsiChar' to 'string'":
function TProj4.pj_strerrno(_ErrorCode: Integer): string;
var
Err: PAnsiChar;
begin
Err := Fpj_strerrno(_ErrorCode);
Result := Err;
end;
EDIT: The text in question is an error message in English, so there are unlikely to be any conversion problems here.
I am now tempted to just explicitly typecast Err to string like this ...
Result := String(Err);
.. to get rid of the warning. Could this go wrong? Should I rather use a temporary AnsiString variable instead?
var
s: AnsiString;
[...]
s := Err;
Result := String(s);
If yes, why?
Or should I make it explicit, that the code first converts a PAnsiChar to AnsiString and then the AnsiString to a String?
Result := String(AnsiString(Err));
And of course I could make it a function:
function PAnsicharToString(_a: PAnsiChar): string;
begin
// one of the above conversion codes goes here
end;
All these options compile, but will they work? And what's the best practice here?
Bonus points: The code should ideally compile and work with Delphi 2007 and newer versions as well.
If the text is encoded in the users current locale then I'd say it is simplest to write:
var
p: PAnsiChar;
str: string;
....
str := string(p);
Otherwise if you wish to convert from a specific code page to a Unicode string then you would use UnicodeFromLocaleChars.
I think the general solution is assigning c char pointer to RawByteString, then set its codepage corresponding to c null-terminated string encoding.
var
bys :TBytes;
rbstr :RawByteString;
ustr :string;
pastr :PAnsiChar;
begin
SetLength(bys,5);
bys[0] := $ca;
bys[1] := $e9;
bys[2] := $d2;
bys[3] := $b5;
bys[4] := 0;
pastr := #bys[0]; // just simulate char* returned by c api
rbstr := pastr; // assign PAnsiChar to RawByteString
// assume text encoded as codepage 936
// Note here: set 3rd param to false!
SetCodePage(rbstr,936,false);
ustr := string(rbstr);
ShowMessage(ustr);
end;
And the other cross-platform solution is (vcl,fmx,fmx with mobile platform)
function CString2TBytes(ptr :{$IFDEF NEXTGEN} MarshaledAString {$ELSE} PAnsiChar {$ENDIF}) :TBytes;
var
pby :PByte;
len :Integer;
begin
pby := PByte(ptr);
while pby^<>0 do Inc(pby);
len := pby - ptr;
SetLength(Result,len);
if len>0 then Move(ptr^,Result[0],len);
end;
procedure TForm5.Button1Click(Sender: TObject);
var
bys, cbys: TBytes;
ustr: string;
// PAnsiChar is undefined in mobile platform
// remap param foo(outSting:PAnsiString) => foo(outString:MarshaledAString)
ptr: {$IFDEF NEXTGEN} MarshaledAString {$ELSE} PAnsiChar {$ENDIF}; //
encoding : TEncoding;
begin
SetLength(bys, 5);
bys[0] := $CA;
bys[1] := $E9;
bys[2] := $D2;
bys[3] := $B5;
bys[4] := 0;
ptr := #bys[0]; // just simulate char* returned by c api
cbys := CString2TBytes(ptr);
// assume text encoded as codepage 936
encoding := TEncoding.GetEncoding(936);
try
ustr := encoding.GetString(cbys);
ShowMessage(ustr);
finally
encoding.Free;
end;
end;

Delphi 2010 Error E2010 Incompatible types: Char' and 'AnsiChar'

I loaded up some code from Delphi and when I compile it inside Delphi 2010 I get an E2010 Incompatible types: 'Char' and 'AnsiChar'.
How do I resolve this error? help please
function TFKirimEmail.ChAnsiToWide(const StrA: AnsiString): WideString;
var
nLen: integer;
begin
Result := StrA;
if Result <> '' then
begin
nLen := MultiByteToWideChar(GetACP(), 1, PChar(#StrA[1]), -1, nil, 0);
SetLength(Result, nLen - 1);
if nLen > 1 then
MultiByteToWideChar(GetACP(), 1, PChar(#StrA[1]), -1, PWideChar(#Result[1]), nLen - 1);
end;
end;
In Delphi 2009 and later, (P)Char is an alias for (P)WideChar, whereas it was an alias for (P)AnsiChar in earlier versions. That is why you are getting a compiler error.
The third parameter of MultiByteToWideChar() expects a PAnsiChar in all versions of Delphi. So simply change PChar to PAnsiChar. Which will work fine considering that StrA is AnsiString and not String (which is an alias for UnicodeString in D2009+), so they will match.
That being said, you should be using the CP_ACP constant instead of the GetACP() function, remove the redundant assignment (and conversion) to Result before calling MultiByteToWideChar(), remove unnecessary character indexing, and remove unnecessary null terminator handling:
function TFKirimEmail.ChAnsiToWide(const StrA: AnsiString): WideString;
var
nLen: integer;
begin
Result := '';
nLen := MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, PAnsiChar(StrA), Length(StrA), nil, 0);
if nLen > 0 then
begin
SetLength(Result, nLen);
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, PAnsiChar(StrA), Length(StrA), PWideChar(Result), nLen);
end;
end;
With that said, don't use WideString in D2009+ for non-ActiveX work. UnicodeString is more efficient.
function TFKirimEmail.ChAnsiToWide(const StrA: AnsiString): UnicodeString;
Lastly, since you are setting the CodePage to ACP and the dwFlags parameter to MB_PRECOMPOSED (which is the default if no other flags are specified), you could just eliminate all this code and let the RTL handle the conversion for you, since it uses those same settings internally by default:
function TFKirimEmail.ChAnsiToWide(const StrA: AnsiString): WideString;
begin
Result := WideString(StrA);
end;
Or:
function TFKirimEmail.ChAnsiToWide(const StrA: RawByteString): UnicodeString;
begin
Result := UnicodeString(StrA);
end;
In which case, your ChAnsiToWide() function becomes redundant and can be eliminated completely.

Delphi: Encoding Strings as Python do

I want to encode strings as Python do.
Python code is this:
def EncodeToUTF(inputstr):
uns = inputstr.decode('iso-8859-2')
utfs = uns.encode('utf-8')
return utfs
This is very simple.
But in Delphi I don't understand, how to encode, to force first the good character set (no matter, which computer we have).
I tried this test code to see the convertion:
procedure TForm1.Button1Click(Sender: TObject);
var
w : WideString;
buf : array[0..2048] of WideChar;
i : integer;
lc : Cardinal;
begin
lc := GetThreadLocale;
Caption := IntToStr(lc);
StringToWideChar(Edit1.Text, buf, SizeOF(buf));
w := buf;
lc := MakeLCID(
MakeLangID( LANG_ENGLISH, SUBLANG_ENGLISH_US),
0);
Win32Check(SetThreadLocale(lc));
Edit2.Text := WideCharToString(PWideChar(w));
Caption := IntToStr(AnsiCompareText(Edit1.Text, Edit2.Text));
end;
The input is: "árvíztűrő tükörfúrógép", the hungarian accent tester phrase.
The local lc is 1038 (hun), the new lc is 1033.
But this everytime makes 0 result (same strings), and the accents are same, I don't lost ŐŰ which is not in english lang.
What I do wrong? How to I do same thing as Python do?
Thanks for every help, link, etc:
dd
Windows uses codepage 28592 for ISO-8859-2. If you have a buffer containing ISO-8859-2 encoded bytes, then you have to decode the bytes to UTF-16 first, and then encode the result to UTF-8. Depending on which version of Delphi you are using, you can either:
1) on pre-D2009, use MultiByteToWideChar() and WideCharToMultiByte():
function EncodeToUTF(const inputstr: AnsiString): UTF8String;
var
ret: Integer;
uns: WideString;
begin
Result := '';
if inputstr = '' then Exit;
ret := MultiByteToWideChar(28592, 0, PAnsiChar(inputstr), Length(inputstr), nil, 0);
if ret < 1 then Exit;
SetLength(uns, ret);
MultiByteToWideChar(28592, 0, PAnsiChar(inputstr), Length(inputstr), PWideChar(uns), Length(uns));
ret := WideCharToMultiByte(65001, 0, PWideChar(uns), Length(uns), nil, 0, nil, nil);
if ret < 1 then Exit;
SetLength(Result, ret);
WideCharToMultiByte(65001, 0, PWideChar(uns), Length(uns), PAnsiChar(Result), Length(Result), nil, nil);
end;
2a) on D2009+, use SysUtils.TEncoding.Convert():
function EncodeToUTF(const inputstr: RawByteString): UTF8String;
var
enc: TEncoding;
buf: TBytes;
begin
Result := '';
if inputstr = '' then Exit;
enc := TEncoding.GetEncoding(28592);
try
buf := TEncoding.Convert(enc, TEncoding.UTF8, BytesOf(inputstr));
if Length(buf) > 0 then
SetString(Result, PAnsiChar(#buf[0]), Length(buf));
finally
enc.Free;
end;
end;
2b) on D2009+, alternatively define a new string typedef, put your data into it, and assign it to a UTF8String variable. No manual encoding/decoding needed, the RTL will handle everything for you:
type
Latin2String = type AnsiString(28592);
var
inputstr: Latin2String;
outputstr: UTF8String;
begin
// put the ISO-8859-2 encoded bytes into inputstr, then...
outputstr := inputstr;
end;
If you're using Delphi 2009 or newer every input from the default VCL controls will be UTF-16, so no need to do any conversions on your input.
If you're using Delphi 2007 or older (as it seems) you are at mercy of Windows, because the VCL is ANSI and Windows has a fixed Codepage that determines which characters can be used in i.e. a TEdit.
You can change the system-wide default ANSI CP in the control panel though, but that requires a reboot each time you do.
In Delphi 2007 you have some chance to use TNTUnicode controls or some similar solution to get the Text from the UI to your code.
In Delphi 2009 and newer there are also plenty of Unicode and character set handling routines in the RTL.
The conversion between character sets can be done with SysUtils.TEncoding:
http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/SysUtils_TEncoding.html
The Python code in your question returns a string in UTF-8 encoding. To do this with pre-2009 Delphi versions you can use code similar to:
procedure TForm1.Button1Click(Sender: TObject);
var
Src, Dest: string;
Len: integer;
buf : array[0..2048] of WideChar;
begin
Src := Edit1.Text;
Len := MultiByteToWideChar(CP_ACP, 0, PChar(Src), Length(Src), #buf[0], 2048);
buf[Len] := #0;
SetLength(Dest, 2048);
SetLength(Dest, WideCharToMultiByte(CP_UTF8, 0, #buf[0], Len, PChar(Dest),
2048, nil, nil));
Edit2.Text := Dest;
end;
Note that this doesn't change the current thread locale, it simply passes the correct code page parameters to the API.
There are encoding tools in Open XML library. There is cUnicodeCodecsWin32 unit with functions like: EncodingToUTF16().
My code that converts between ISO Latin2 and UTF-8 looks like:
s2 := EncodingToUTF16('ISO-8859-2', s);
s2utf8 := UTF16ToEncoding('UTF-8', s2);

ShLwApi.StrFormatByteSize and Delphi 2010 Unicode

Can someone help me fix this:
{$IFDEF UNICODE}
function FormatStringByteSize( TheSize: Cardinal ): string;
{ Return a cardinal as a string formated similar to the statusbar of Explorer }
var
Buff: string;
Count: Integer;
begin
Count := Length(Buff);
FillChar(Buff, Count, 0);
ShLwApi.StrFormatByteSize( TheSize, PWideChar(Buff), Length( Buff ) * SizeOf( WideChar ) );
Result := Buff;
end;
{$ENDIF}
At least in Delphi 2009 (can't test in version 2010 as I don't have it) the StrFormatByteSize() function is an alias to the Ansi version (StrFormatByteSizeA()), not to the wide char version (StrFormatByteSizeW()) as it is for most of the other Windows API functions. Therefore you should use the wide char version directly - also for earlier Delphi versions, to be able to work with file (system) sizes larger than 4 GB.
There's no need for an intermediate buffer, and you can make use of the fact that StrFormatByteSizeW() returns a pointer to the converted result as a PWideChar:
{$IFDEF UNICODE}
function FormatStringByteSize(ASize: int64): string;
{ Return a cardinal as a string formatted similar to the status bar of Explorer }
const
BufLen = 20;
begin
SetLength(Result, BufLen);
Result := StrFormatByteSizeW(ASize, PChar(Result), BufLen);
end;
{$ENDIF}
You need to set the length of buff first. (Length buff = 0)
Then
Change TheSize to Int64 - you need this for sizes > 4GB anyway.
Maybe change the call to StrFormatByteSizeW (the Delphi "headers" should have done this in D2009+)
In spite of the name, FillChar expects the size to be in bytes, not characters. However this won't affect the result.
function FormatStringByteSize( TheSize: int64 ): string;
// Return an Int64 as a string formatted similar to the status bar of Explorer
var
Buff: string;
begin
SetLength(Buff, 20);
ShLwApi.StrFormatByteSizeW( TheSize, PWideChar(Buff), Length(Buff));
Result := PChar(Buff);
end;
I can't test this in D2009/10 at moment as haven't started the move to Unicode yet (next project!) It works in D2006 with WideString.

Resources