I have created a TTextLayout object with text containing consecutive 't' characters and with the 'Calibri' font. I then have the following code to return the rectangular region of each character using the RegionForRange function. The result is that the width of the 1st 't' is 0 and the position of the 2nd 't' is the same as the first. Any other characters in the text are correct - even ones after the error, although the letter 'f' also has the same problem and any consecutive combination of 't' and 'f'. Most other fonts don't seem to cause the problem, although 'Gabriola' does.
procedure TForm1.FormCreate(Sender: TObject);
var
Layout : TTextLayout;
LRange : TTextRange;
LRegion : TRegion;
LRects : array of TRectF;
i : Integer;
begin
Layout := TTextLayoutManager.DefaultTextLayout.Create;
Layout.Font.Size := 20;
// Calibra and Gabriola fail but Arial and most other fonts don't
Layout.Font.Family := 'Calibri';// 'Gabriola';
Layout.Text := 'tt'; // ff, ft, tf also fail
LRange.Length := 1;
SetLength(LRects, Length(Layout.Text));
for i := 0 to Length(Layout.Text) - 1 do begin
LRange.Pos := i;
LRegion := Layout.RegionForRange(LRange);
LRects[i] := LRegion[0]; // Bounding rect of this character
end;
end;
Put a break point at the end of the function to see the values of left and right stored in LRects.
Stepping into the RegionForRange function leads to TTextLayoutD2D.DoRegionForRange but from there I can't go any further to see what could be going wrong. Why could this be happening for these particular characters and only for these fonts? Why should the character following the one in the range affect the result? Is it a bug? I could perhaps write some code to detect these sequences and correct the position, but I don't feel that I should need to do that.
Note that I'm using Delphi 10.4. I have not tried more recent updates, so I would appreciate if someone could confirm that this issue occurs and in which version.
The reason is called ligature.
When a ligature is applied, a combination of two or more glyphs is replaced by another, single glyph.
Ligatures are defined by OpenType fonts. Each font defines its own set of ligatures. Calibri defines a lot (you have found only a few ones).
Related
I have a TRichEdit with some RTF in it (text with formatting only) and I want to know if all the content of the TRichEdit is selected. To do so, I do:
var AllSelectd: Boolean;
//...
AllSelectd := MyRichEdit.SelLength = MyRichEdit.GetTextLen;
which works fine, except when the content has three lines or more. With zero to two lines, everything is fine. As soon as I reach three lines in my TRichEdit, the code above no longer works (MyRichEdit.SelLength < MyRichEdit.GetTextLen). Each line is terminated with CRLF (#13#10).
Is this a bug? How can I reliably check if everything is selected in the TRichEdit?
I use Delphi 10.4, if it changes anything.
As mentioned in this topic, RichEdit 2.0 replaces CRLF pairs with CR internally, and retrieves LF's in some cases.
As workaround - calculate number of lines in selected range to make correction (SelText contains only CR's, GetTextLen works with text with retrieved CRLF, so counts both CR's and LF's). Remy Lebeau proposal is used.
var
sel, getl, crcnt, i: integer;
tx: string;
begin
sel := RichEdit1.SelLength;
getl := RichEdit1.GetTextLen;
crcnt := SendMessage(Richedit1.Handle, EM_EXLINEFROMCHAR, 0, sel);
Memo1.Lines.Add(Format('%d %d',[sel, getl - crcnt + 1]));
end;
Investigating odd behaviour of a TValueListEditor being used to generate a filter
expression for a ClientDataSet, I've traced it to a situation where if the first entry
in it apparently had nothing in the Value column, it returned #13#10 as the Value, rather than
''.
In the following, the TStringlist TL is initialized with the same contents as the ValueListEditor
Strings property has in my app. The Assert does not fail for the TStringlist, but it does for the
ValueListEditor. These results occurred with D7 and XE4.
procedure TDefaultForm.ApplyFilter;
var
i,
Max : Integer;
Key,
Value : String;
TL : TStringlist;
begin
TL := TStringlist.Create;
try
TL.Add('Country=');
TL.Add('Class=CON');
for i:= 0 to TL.Count - 1 do begin
Key := TL.Names[i];
Value := TL.Values[Key];
Assert(Value <> #13#10); // succeeds for all i
end;
Max := ValueListEditor1.RowCount;
for i:= 1 to Max do begin
Key := ValueListEditor1.Keys[i];
Value := ValueListEditor1.Values[Key];
// Value := ValueListEditor1.Strings.ValueFromIndex[i-1];
Assert(Value <> #13#10); //Fails for i = 1!
end;
finally
TL.Free;
end;
end;
Btw, the TVLE was set up entirely in the Object Inspector: I simply dragged a TVLE off the palette, clicked Strings in the OI, clicked in the LH cell and typed 'Country' (sans quotes), pressed the Down key and typed 'Class' then right-arrow and typed 'CON'.
Obviously, I could avoid this by Value := Trim(Value), but was curious where the #13#10 was coming from.
Update: Prompted by #Deltic's answer and helpful comments, I decided to re-trace my steps and added another TVLE to my form. The following extracts from the DFM are revealing:
object ValueListEditor1: TValueListEditor
Left = 16
Top = 224
Width = 306
Height = 135
KeyOptions = [keyEdit, keyAdd]
Strings.Strings = (
'Country='#13#10
'Class=CON')
TabOrder = 2
end
[...]
object ValueListEditor2: TValueListEditor
Left = 440
Top = 192
Width = 306
Height = 246
KeyOptions = [keyEdit, keyAdd]
Strings.Strings = (
'A='
'B=ValueOfB')
TabOrder = 5
end
So, with hindsight, my question really boils down to how did the #13#10 get into the DFM? And then it came back to me ...
With no previous experience of the TVLE, when I set up the form, I got stuck at the point where I needed to add a second row. I tried pressing [Enter], but that did nothing, so then I tried Ctrl-Enter and that did nothing either. But repeating the exercise now has confirmed that that's how the CR/LF got into the TVLE's Strings.
So, it seems that the answer to my q is "No, the TVLE isn't broken, but its Strings property editor
has a quirk regarding Ctrl-Enter". In other circs, I would consider deleting my q, seeing as it's at least partly caused by operator aberration, but perhaps it's better left to assist any others who trip over the same point.
Update #2 I see that my curiousity has earned me a -1. Fair enough, but I'm still inclined to leave this q & a in place, if only as an illustration of the fact that problems have deterministic causes, which can often be identified by simple things such as re-tracing one's steps, particularly with someone obviously knowledgeable looking over one's shoulder, as it were. Perhaps the down-voter would care to enlighten readers what help to future readers such a silent -1 is.
You have not shown how your value list editor is initialised, and I suspect that this is where your problem is. Behind a TValueListEditor is nothing more than a TStringList (strictly speaking a subclass of one, but the subclass doesn't change the fundamental behaviour w.r.t named values).
If your apparently empty value in the value list is yielding a value of #13#10 then it must be because that is the actual value that it has.
This simple test snippet verifies this:
var
i:Integer;
k, v:String;
begin
ed.InsertRow('Country', '', TRUE);
ed.InsertRow('Class', 'CON', TRUE);
for i:= 1 to ed.RowCount - 1 do
begin
k := ed.Keys[i];
v := ed.Values[k];
ASSERT(v <> #13#10); // Never fails
end;
end;
Where ed is a TValueListEditor on the form.
Replace the first line of code in the above snippet with this however:
ed.InsertRow('Country', #13#10, TRUE);
And the ASSERT() fails.
I suggest you investigate the initialisation of your value list editor. My guess is that it is being populated by reading from a file using a mechanism which is reading the entire line into a string, including the line end sequences, and the code that is adding the values for each read line is not stripping the #13#10 line terminators, resulting in the values being added as <name>=<value>#13#10 in each case.
I am writing a Global Hook Procedure. Using SendInput to send ascii ( < 255 ) and Unicode characters. So far it is working well.
But in my language, Tamil there are some characters that are combination characters which do not have Unicode code points. So in fonts there are glyphs defined above 255.
How do I send virtual key codes for them?
Though those glyphs do not have code points they have glyph names like u0BAF_u0BBF. the first and second part are defined in Unicode. These names are referred as named sequence and approved by Unicode.
Code:
procedure GenUKey(const vk: integer; const bUnicode: bool);
var
kb: TKEYBDINPUT;
Input: TINPUT;
begin
{keydown}
{ ZeroMemory(#kb,sizeof(kb));}
{ ZeroMemory(#input,sizeof(input));}
{keydown}
if bUnicode then
begin
kb.wVk:= 0;
kb.wScan:= vk; ;
kb.dwFlags:= $4;
{ KEYEVENTF_UNICODE=4}
end
else
begin
kb.wVk:= vk;
kb.wScan:= 0; ;
kb.dwFlags:= 0;
end;
Input.itype:= INPUT_KEYBOARD;
Input.ki:= kb;
SendInput(1, Input,sizeof(Input));
{keyup}
if bUnicode then
begin
kb.wVk:= 0;
kb.wScan:= vk ;
kb.dwFlags:= $4 or KEYEVENTF_KEYUP;
{KEYEVENTF_UNICODE=4}
end
else
begin
kb.wVk:= vk;
kb.wScan:= 0;
kb.dwFlags:= KEYEVENTF_KEYUP;
end;
Input.itype:= INPUT_KEYBOARD;
Input.ki:= kb;
SendInput(1,Input,sizeof(Input));
end;
Edit:
Yes, I do send each code point individually. There are glyphs attached to each code point. Also there is another one combining the design of both. It looks better visually and behaves as one character. As it is defined separately I thought there should be a way to send by using sendinput. Edit2:
Also there are some characters that can not be combined mechanically.
Edit 3:
Tests prove that Rob Kennedy is correct. The second code point is not displayed mechanically. It is intelligently combined with the first one to get a third new combined glyph. The glyph (name) encoding is used. My code to do the same programmatically has interfered in this process and hence the problem. Many Thanks
I am trying to get a routine that will find a string that does not follow a parentheses. For instance if the file open in the RichEdit contains these lines of CNC code, I want it to find the first two and ignore the third. In the second line it should only find and highlight the first occurrence of the search string. The search string (mach.TOOL_CHANGE_CALL) in this example is 'T'.
N1T1M6
N1T1M6(1/4-20 TAP .5 DP.)
(1/4-20 TAP .5 DP.)
I have gotten this far, but am stumped.
procedure TMainForm.ToolButton3Click(Sender: TObject); // find tool number
var
row:integer;
sel_str:string;
par:integer;
tool:integer;
tool_flag:integer ;
line_counter:integer;
tool_pos:integer;
line_begin:integer;
RE:TRichEdit;
begin
RE:=(ActiveMDIChild as TMDIChild).RichEdit1;
line_counter:=0;
tool_flag:=0;
tool_pos:=0;
row:=SendMessage(RE.Handle,EM_LINEFROMCHAR,-1, RE.SelStart);
while tool_flag =0 do
begin
RE.Perform(EM_LINESCROLL,0,line_counter);
sel_str := RE.Lines[Line_counter];
tool:=pos(mach.TOOL_CHANGE_CALL,sel_str);
par:=pos('(',sel_str);
if par=0 then
par:=pos('[',sel_str);
tool_pos:=tool_pos+length(sel_str);
if (tool>0) and (par = 0) then
begin
RE.SetFocus;
tool_pos:=tool_pos + line_counter-1;
line_begin:=tool_pos-tool;
RE.SelStart := line_begin;
RE.SelLength := Length(sel_str);
tool_flag:=1;
end;
inc (line_counter);
end;
end;
The results I get is that it will ignore the third string, but will also ignore the second string as well. It also will not find subsequent occurrences of the string in the file, it just starts back at the beginning to the text and finds the first one again. How can I get it to find the second example and then find the next 'T' at the next click of the button? I also need it to highlight the entire line the search string was found on.
Given the samples you posted, you can use Delphi (XE and higher) regular expressions to match the text you've indicated. Here, I've put the three sample lines you've shown into a TMemo (Memo1 in the code below), evaluate the regular expression, and put the matches found into Memo2 - as long as your TRichEdit contains only plain text, you can use the same code by replacing Memo1 and Memo2 with RichEdit1 and RichEdit2 respectively.
I've updated the code in both snippets to show how to get the exact position (as an offset from the first character) and length of the match result; you can use this to highlight the match in the richedit using SelStart and SelLength.
uses
RegularExpressions;
procedure TForm1.Button1Click(Sender: TObject);
var
Regex: TRegEx;
MatchResult: TMatch;
begin
Memo1.Lines.Clear;
Memo1.Lines.Add('N1T1M6');
Memo1.Lines.Add('N1T1M6(1/4-20 TAP .5 DP.)');
Memo1.Lines.Add('(1/4-20 TAP .5 DP.)');
Memo2.Clear;
// See the text below for an explanation of the regular expression
Regex := TRegEx.Create('^\w+T\w+', [roMultiLine]);
MatchResult := Regex.Match(Memo1.Lines.Text);
while MatchResult.Success do
begin
Memo2.Lines.Add(MatchResult.Value +
' Index: ' + IntToStr(MatchResult.Index) +
' Length: ' + IntToStr(MatchResult.Length));
MatchResult := MatchResult.NextMatch;
end;
end;
This produces the following results:
If you're using a version of Delphi that doesn't include regular expression support, you can use the free TPerlRegEx with some minor code changes to produce the same results:
uses
PerlRegEx;
procedure TForm1.Button1Click(Sender: TObject);
var
Regex: TPerlRegEx;
begin
Memo1.Lines.Clear;
Memo1.Lines.Add('N1T1M6');
Memo1.Lines.Add('N1T1M6(1/4-20 TAP .5 DP.)');
Memo1.Lines.Add('(1/4-20 TAP .5 DP.)');
Memo2.Clear;
Regex := TPerlRegEx.Create;
try
Regex.RegEx := '^\w+T\w+';
Regex.Options := [preMultiLine];
Regex.Subject := Memo1.Lines.Text;
if Regex.Match then
begin
repeat
Memo2.Lines.Add(Regex.MatchedText +
' Offset: ' + IntToStr(Regex.MatchedOffset) +
' Length: ' + IntToStr(Regex.MatchedLength));
until not Regex.MatchAgain;
end;
finally
Regex.Free;
end;
end;
The regular expression above (^\w+T\w+) means:
Options: ^ and $ match at line breaks
Assert position at the beginning of a line (at beginning
of the string or after a line break character) «^»
Match a single character that is a “word character” (letters,
digits, and underscores) «\w+»
Between one and unlimited times, as many times as possible,
giving back as needed (greedy) «+»
Match the character “T” literally «T»
Match a single character that is a “word character” (letters,
digits, and underscores) «\w+»
Between one and unlimited times, as many times as possible,
giving back as needed (greedy) «+»
Created with RegexBuddy
You can find a decent tutorial regarding regular expressions here. The tool I used for working out the regular expression (and actually producing much of the Delphi code for both examples) was RegexBuddy - I'm not affiliated with the company that produces it, but just a user of that product.
When transitioning from Delphi 2006 to Delphi XE2, one of the things that we learned is that RichEdit 2.0 replaces internally CRLF pairs with a single CR character. This has the unfortunate effect of throwing off all character index calculations based on the actual text string on the VCL's side.
The behavior I can see by tracing through the VCL code is as follows:
Sending a WM_GETTEXT message (done in TControl.GetTextBuf) will return a text buffer that contains CRLF pairs.
Sending a WM_GETTEXTLENGTH message (done in TControl.GetTextLen) will return a value as if the text still contains CRLF characters.
In contrast, sending an EM_SETSELEX message (i.e. setting SelStart) will treat the input value as if the text contains only CR characters.
This causes all sorts of things to fail (such as syntax highlighting) in our application. As you can tell, everything is off by exactly one character for every new line up to that point.
Obviously, since this is inconsistent behavior, we must be missing something or doing something very wrong.
Does anybody else has any experience with the transition from a RichEdit 1.0 to a RichEdit 2.0 control and how did you solve this issue? Finally, is there any way to force RichEdit 2.0 to use CRLF pairs just like RichEdit 1.0?
We also ran into this very issue.
We do a "mail merge" type of thing where we have templates with merge codes that are parsed and replaced by data from outside sources.
This index mismatch between pos(mystring, RichEdit.Text) and the positioning index into the RichEdit text using RichText.SelStart broke our merge.
I don't have a good answer but I came up with a workaround. It's a bit cumbersome (understatment!) but until a better solution comes along...
The workaround is to use a hidden TMemo and copy the RichEdit text to it and change the CR/LF pairs to CR only. Then use the TMemo to find the proper positioning using pos(string, TMemo) and use that to get the selstart position to use in the TRichEdit.
This really sucks but hopefully this workaround will help others in our situation or maybe spark somebody smarter than me into coming up with a better solution.
I'll show a little sample code...
Since we are replacing text using seltext we need to replace text in BOTH the RichEdit control and the TMemo control to keep the two synchronized.
StartToken and EndToken are the merge code delimiters and are a constant.
function TEditForm.ParseTest: boolean;
var TagLength: integer;
var ValueLength: integer;
var ParseStart: integer;
var ParseEnd: integer;
var ParseValue: string;
var Memo: TMemo;
begin
Result := True;//Default
Memo := TMemo.Create(nil);
try
Memo.Parent := self;
Memo.Visible := False;
try
Memo.Lines.Clear;
Memo.Lines.AddStrings(RichEditor.Lines);
Memo.Text := stringreplace(Memo.Text,#13#10,#13,[rfReplaceAll]);//strip CR/LF pairs and replace with CR
while (Pos(StartToken, Memo.Text) > 0) and (Pos(EndToken, Memo.Text) > 0) do begin
ParseStart := Pos(StartToken, Memo.SelText);
ParseEnd := Pos(EndToken, Memo.SelText) + Length(EndToken);
if ParseStart >= ParseEnd then begin//oops, something's wrong - bail out
Result := true;
myEditor.SelStart := 0;
exit;
end;
TagLength := ParseEnd - ParseStart;
ValueLength := (TagLength - Length(StartToken)) - Length(EndToken);
ParseValue := Copy(Memo.SelText, (ParseStart + Length(StartToken)), ValueLength);
Memo.selstart := ParseStart - 1; //since the .text is zero based, but pos is 1 based we subtract 1
Memo.sellength := TagLength;
RichEditor.selstart := ParseStart - 1; //since the .text is zero based, but pos is 1 based we subtract 1
RichEditor.sellength := TagLength;
TempText := GetValue(ParseValue);
Memo.SelText := TempText;
RichEditor.SelText := TempText;
end;
except
on e: exception do
begin
MessageDlg(e.message,mtInformation,[mbOK],0);
result := false;
end;
end;//try..except
finally
FreeAndNil(Memo);
end;
end;
How about subtracting EM_LINEFROMCHAR from the caret position? (OR the position of EM_GETSEL) whichever you need.
You could even get two EM_LINEFROMCHAR variables. One from the selection start and the other from the desired caret/selection position, if you only want to know how many cl/cr pairs are in the selection.