Text Rechteck ermitteln

Für allgemeine Fragen zur Programmierung, welche nicht! direkt mit Lazarus zu tun haben.
siro
Beiträge: 730
Registriert: Di 23. Aug 2016, 14:25
OS, Lazarus, FPC: Windows 11
CPU-Target: 64Bit
Wohnort: Berlin

Text Rechteck ermitteln

Beitrag von siro »

Hallo,

ich schreibe grade an einer neuen Komponente, im Prinzip ein rotierter Label und da habe ich Probleme
mit dem Berechnen des benötigten Textrechteckes.

Eigentlich dachte ich, dass dafür die Funktion TextExtent zuständig ist.
Das scheint aber nicht richtig zu funktionieren.
Habe das auch mit verschiedenen Fonts und Größen probiert.

TextExtent liefert die gleichen "falschen" Werte wie TextWidth und Textheight bei gedrehtem Text

auch die GetTextExtendPoint32 liefert falsche Ergebnisse.
GetTextExtentPoint32(canvas.Handle,pchar(s),length(s),size);

Code: Alles auswählen

procedure TForm1.FormPaint(Sender: TObject);
var size:TSize;  s:string;
begin
  s:='Hallo';
  canvas.Font.Orientation:=900;       // Textausgabe 90,0 Grad drehen
 
  size:=canvas.TextExtent(s);         // Das benötigte Textrechteck ermitteln
  label1.caption:=IntToStr(size.cx)// Ausgabe der Breite
  label2.caption:=IntToStr(size.cy)// Ausgabe der Höhe
  canvas.TextOut(100,100,s);          // Text mal angucken :-)
 
  label3.caption:=IntToStr(canvas.TextWidth(s));
  label4.caption:=IntToStr(canvas.TextHeight(s));
 
  // neuer Test
  GetTextExtentPoint32(canvas.Handle,pchar(s),length(s),size);
  label3.caption:=IntToStr(size.cx)// Ausgabe der Breite
  label4.caption:=IntToStr(size.cy)// Ausgabe der Höhe
 
end;


Bei 90 Grad brauche ich ja nur die cx mit cy tauschen, kein Problem, aber bei anderen Winkeln ?

Habe das nur mit Windows testen können.
Habt Ihr da eine Idee, wie man das richtig macht ?

Siro
Grüße von Siro
Bevor ich "C" ertragen muß, nehm ich lieber Lazarus...

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Text Rechteck ermitteln

Beitrag von wp_xyz »

Canvas.TextHeight und Canvas.TextWidth übergeben genau das, wie sie heißen: die Höhe und Breite des Texts, ungedreht. Um die Eckpunkte des gedrehten, den Text umhüllenden Rechtecks zu bestimmen, brauchst du etwas Mathematik mit sin und cos. Hier eine Funktion, geklaut aus TAChart:

Code: Alles auswählen

uses
  math;
 
function RotatePoint(const APoint: TPoint; AAngle: Double): TPoint;
var
  sa, ca: Extended;
begin
  SinCos(AAngle, sa, ca);
  Result.X := Round(ca * APoint.X - sa * APoint.Y);
  Result.Y := Round(sa * APoint.X + ca * APoint.Y);
end;

Der Winkel ist entgegen dem Uhrzeigersinn zu nehmen und wird im Bogenmaß verlangt, nimm DegToRad aus der Unit math, um von Grad nach Bogenmaß umzurechnen. Das Drehzentrum ist der Ursprung. Du musst nun einfach diese Drehung auf die vier Eckpunkte des den Text umgebenden Rechtecks anwenden und dann das gedrehte Rechteck an die Textposition schieben (Koordinaten der Textposition addieren).

Eine Demo ist im Anhang, drehe den Text mit dem Schieber.
Dateianhänge
Rotated_Text_Rect.zip
(2.59 KiB) 127-mal heruntergeladen

siro
Beiträge: 730
Registriert: Di 23. Aug 2016, 14:25
OS, Lazarus, FPC: Windows 11
CPU-Target: 64Bit
Wohnort: Berlin

Re: Text Rechteck ermitteln

Beitrag von siro »

Guten Morgen,
ja was soll ich sagen : EINFACH NUR GEIL
Der Dank geht mal wieder an wp_xyz.

Ich hatte schon angefangen, die Drehung mit den Eckpunkten selbst zu berechnen, dafür hatte ich schon vor 20 Jahren mal eine Funktion geschrieben.
Wie dem auch sei, ich bin absolut begeistert.

Siro


Bei MinValue und MaxValue braucht man meiner Meinung nach nur 4 Werte übergeben, der fünfte dient lediglich dem Schließen des Polygons und ist identisch zum Ersten.
Ein Verusch konnte dies bestätigen.

Code: Alles auswählen

  // Das den gedrehten Text einhüllende, achsenparallele Rechteck bestimmen...
//  R.Left := MinValue([P[0].X, P[1].X, P[2].X, P[3].X, P[4].X]);
//  R.Right := MaxValue([P[0].X, P[1].X, P[2].X, P[3].X, P[4].X]);
//  R.Top := MinValue([P[0].Y, P[1].Y, P[2].Y, P[3].Y, P[4].Y]);
//  R.Bottom := MaxValue([P[0].Y, P[1].Y, P[2].Y, P[3].Y, P[4].Y]);
 
// geändert Siro: nur 4 Koordinaten übergeben
  R.Left   := MinValue([P[0].X, P[1].X, P[2].X, P[3].X]);
  R.Right  := MaxValue([P[0].X, P[1].X, P[2].X, P[3].X]);
  R.Top    := MinValue([P[0].Y, P[1].Y, P[2].Y, P[3].Y]);
  R.Bottom := MaxValue([P[0].Y, P[1].Y, P[2].Y, P[3].Y])
 
Grüße von Siro
Bevor ich "C" ertragen muß, nehm ich lieber Lazarus...

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Text Rechteck ermitteln

Beitrag von wp_xyz »

siro hat geschrieben:Bei MinValue und MaxValue braucht man meiner Meinung nach nur 4 Werte übergeben, der fünfte dient lediglich dem Schließen des Polygons und ist identisch zum Ersten.

Gut beobachtet. Versuch mal als "Hausaufgabe", den Text um sein Zentrum zu drehen.

siro
Beiträge: 730
Registriert: Di 23. Aug 2016, 14:25
OS, Lazarus, FPC: Windows 11
CPU-Target: 64Bit
Wohnort: Berlin

Re: Text Rechteck ermitteln

Beitrag von siro »

wp_xyz hat geschrieben:
siro hat geschrieben:Bei MinValue und MaxValue braucht man meiner Meinung nach nur 4 Werte übergeben, der fünfte dient lediglich dem Schließen des Polygons und ist identisch zum Ersten.

Gut beobachtet. Versuch mal als "Hausaufgabe", den Text um sein Zentrum zu drehen.


Hab ich gemacht, alles komplett neu geschrieben und es läuft. Habe dafür eine Procedure RotatePolygon geschrieben, die ich hier gern zur Verfügung stelle.
Hier wird ein beliebiges Polygon um einen beliebigen Drehpunkt rotiert:

Code: Alles auswählen

{------------------------------------------------------------------------------}
procedure RotatePolygon(out Points    : array of TPoint;  // Alle Polygonpunkte (das Polygon)
                        RotationPoint : TPoint;           // Punkt der Rotation
                        Angle         : Double);          // Drehwinkel in Grad 0..360 entgegen dem Uhrzeigersinn
var i:Integer;
var p:TPoint;  { !!! Zwischenvariable zwingend erforderlich }
begin
  // wenn im Uhrzeigersinn gedreht werden soll, einfach das Minus wegnehmen
  Angle := -Angle * pi / 180;       // Winkel Grad nach Rad(Bogenmaß) wandeln
 
  for i:=0 to High(Points) do begin    // alle Polygonpunkte durchlaufen
 
    { neue X Kooordinate des Punktes berechnen }
    p.x:=round( RotationPoint.x                              // Original Position
             + (Points[i].x - RotationPoint.x) * cos(Angle)  // + deltaX * cos(w)
             - (Points[i].y - RotationPoint.y) * sin(Angle)  // - deltaY * sin(w)
              );
 
    { neue Y Kooordinate des Punktes berechnen }
    p.y:=round( RotationPoint.y                              // Original Position
             + (Points[i].x - RotationPoint.x) * sin(Angle)  // + deltaX * sin(w)
             + (Points[i].y - RotationPoint.y) * cos(Angle)  // + deltaY * cos(w)
              );
 
    { ! erst jetzt die neuen beiden Werte für x und y übernehmen }
    Points[i].x:=p.x;
    Points[i].y:=p.y;
  end;
end;                                     
 
 


Dazu gibt es noch das Verschieben eines Polygons: dx dy könnte auch Integer sein :wink:

Code: Alles auswählen

{ verschiebt ein beliebiges Polygon horizontal dx und vertikal dy }
Procedure MovePolygon(out Points: array of TPoint; dx,dy:Double);
var i:Integer;
begin
  for i:=0 to High(Points) do begin
    Points[i].x:=round(Points[i].x + dx);
    Points[i].y:=round(Points[i].y + dy);
  end;
end;                 
Grüße von Siro
Bevor ich "C" ertragen muß, nehm ich lieber Lazarus...

siro
Beiträge: 730
Registriert: Di 23. Aug 2016, 14:25
OS, Lazarus, FPC: Windows 11
CPU-Target: 64Bit
Wohnort: Berlin

Re: Text Rechteck ermitteln

Beitrag von siro »

Warum hab ich das so betont: !!! Zwischenvariable zwingend erforderlich :roll:

weil ich das Anfangs so hatte:

Code: Alles auswählen

 
  Points[i].x:=round( RotationPoint.x                        // Original Position
             + (Points[i].x - RotationPoint.x) * cos(Angle)  // + deltaX * cos(w)
             - (Points[i].y - RotationPoint.y) * sin(Angle)  // - deltaY * sin(w)
              );
  
  Points[i].y:=round( RotationPoint.y                        // Original Position
             + (Points[i].x - RotationPoint.x) * sin(Angle)  // + deltaX * sin(w)
             + (Points[i].y - RotationPoint.y) * cos(Angle)  // + deltaY * cos(w)
              );
 



Das Problem: bei der neunen y-Berechnung war die X Koordinate ja schon gedreht und
dann stimmte die folgende Berechnung für Y nicht mehr
Hat ne Weile gedauert bis drauf kam.. :P sah aber lustig aus beim Drehen :lol:
Grüße von Siro
Bevor ich "C" ertragen muß, nehm ich lieber Lazarus...

siro
Beiträge: 730
Registriert: Di 23. Aug 2016, 14:25
OS, Lazarus, FPC: Windows 11
CPU-Target: 64Bit
Wohnort: Berlin

Re: Text Rechteck ermitteln

Beitrag von siro »

Einen schönen Ostersonntag vorab,
leider habe ich ein Problem mit meiner rotierten Labelkomponente:
Sie ist abgeleitet von TCustomLabel

Wenn ich den Winkel einstelle, muss width und height neu berechnet werden.
Und genau hier stellt sich die Frage, wo mache ich das ?

In der Methode paint, schmiert Lazarus ab, das leuchtet mir auch ein,
denn wenn ich width setze wird Invalidate ausgeführet was wieder zu paint führt
und das gibt vermutlich eine Rekursion.
Also muss meine Berechnung woanders hin, aber wo ?

Ich hab dann mal die Methode AdjustSize überschrieben, dort scheint es auch fehl am Platze,
denn wenn ich die Komponente aufs Formular ziehe, bekomme ich eine merkwürdige Fehlermeldung.

Dann hab ich mich an die DoAutoSize Methode gewagt und überschrieben.
AdjustSize sorgt anscheinend dafür, das DoAutoSize überhaupt aufgerufen wird.
denn wenn ich dort NICHT inherited aufrufe, so wird DoAutoSize auch nicht mehr aufgerufen.
Also hab ich die Berechnung dort reingeschrieben. Ohne Inherited
Das funktioniert leider auch nicht richtig, aber schon besser.....

Code: Alles auswählen

  CalcTextRect(canvas,caption,r);
  width :=ABS(r.Right  - r.Left);
  height:=ABS(r.Bottom - r.Top);


Wo rufe ich also am besten meine Neuberechnung auf, welche die neue Breite und Höhe setzt ?

Ich hoffe, ihr könnt mir da weiterhelfen.

Siro
Grüße von Siro
Bevor ich "C" ertragen muß, nehm ich lieber Lazarus...

Benutzeravatar
theo
Beiträge: 10467
Registriert: Mo 11. Sep 2006, 19:01

Re: Text Rechteck ermitteln

Beitrag von theo »

siro hat geschrieben:Wenn ich den Winkel einstelle, muss width und height neu berechnet werden.
Und genau hier stellt sich die Frage, wo mache ich das ?


Warum nicht dort, bzw. in dem Moment wo du den Winkel einstellst oder den Text änderst?

siro
Beiträge: 730
Registriert: Di 23. Aug 2016, 14:25
OS, Lazarus, FPC: Windows 11
CPU-Target: 64Bit
Wohnort: Berlin

Re: Text Rechteck ermitteln

Beitrag von siro »

Hallo Theo, ja das habe ich auch als erstes so gemacht.
Dann muss ich zwar AutoSize auf False setzen, aber das ist erstmal nicht das Problem

Wenn ich aber den Winkel ändere, scheint es so ,als wenn die Größe erst bei der nächsten Änderung übernommen wird.
Irgendwie als hinkt meine Funktion immer eine Änderung zurück.
Sehr merkwürdig, das Verhalten.
Grüße von Siro
Bevor ich "C" ertragen muß, nehm ich lieber Lazarus...

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Text Rechteck ermitteln

Beitrag von wp_xyz »

Könntest du mal den Code der Komponente hochladen?

siro
Beiträge: 730
Registriert: Di 23. Aug 2016, 14:25
OS, Lazarus, FPC: Windows 11
CPU-Target: 64Bit
Wohnort: Berlin

Re: Text Rechteck ermitteln

Beitrag von siro »

Oh Mann,
nachdem ich eine komplett abgespeckte neue Komponente kreiert habe,
wurde der Fehler plötzlich offensichtlich.

Ich habe bei SetAngle lediglich den Winkel gesetzt.
Aber erst in der paint Methode Font.Orientation gesetzt... :oops:
Da war die Berechnung aber schon vorbei und funktionierte
demnach erst beim nächsten paint.
Das hat ja gedauert... :lol:
Grüße von Siro
Bevor ich "C" ertragen muß, nehm ich lieber Lazarus...

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Text Rechteck ermitteln

Beitrag von wp_xyz »

Ich glaube nicht, dass es mit SetAngle getan ist. Was ist, wenn Text eingetippt wird und das Label schon gedreht ist? Oder die Schriftgröße geändert wird? Falls sich in diesem Fall die Größe des Labels automatisch anpassen soll, musst du dich doch wieder irgendwie in den AutoSize-Mechanismus einklinken. Da DoAutoSize die Methode GetPreferredSize aufruft und diese wiederrum CalculatePreferredSize, erscheint mir die letztere geeignet zu sein.

siro
Beiträge: 730
Registriert: Di 23. Aug 2016, 14:25
OS, Lazarus, FPC: Windows 11
CPU-Target: 64Bit
Wohnort: Berlin

Re: Text Rechteck ermitteln

Beitrag von siro »

Da hast Du völlig recht: Damit die Anpassung auch bei Textänderungen erfolgt hab ich es jetzt so gemacht:
Das macht vorerst auch einen guten Eindruck. Habe aber leider schon wieder neue Probleme....

Code: Alles auswählen

 
procedure TRotateLabel.Calculate;
var r:Trect;
begin
  if NOT AutoSize then exit;
 
  CalcTextRect(r);
  SetBounds(r.left,r.top,abs(r.right-r.Left),abs(r.bottom-r.top));    // P R O B L E M  TControl.ChangeBounds Loop detect
end;
 
procedure TRotateLabel.SetAngle(newAngle:Integer);
begin
  if newAngle = fAngle then exit;
  fAngle:=NewAngle;
 
  Font.Orientation:=fAngle*10;
  Calculate;
end;
 
procedure TRotateLabel.DoAutoSize;
begin
  Calculate;
// kein Inherited, weil ich ja selber alles berechne.
end;     
 


Neues Problem:
Ich berechne die komplette Komponente neu bzw. Left Top Width und Height, anhand des Winkels
Wenn ich jetzt SetBounds benutze, gibt es wieder eine Endlosschleife.
Das gestaltet sich wirklich schwierig.... hätte ich nicht gedacht.
Grüße von Siro
Bevor ich "C" ertragen muß, nehm ich lieber Lazarus...

Benutzeravatar
theo
Beiträge: 10467
Registriert: Mo 11. Sep 2006, 19:01

Re: Text Rechteck ermitteln

Beitrag von theo »

siro hat geschrieben:Neues Problem:
Ich berechne die komplette Komponente neu bzw. Left Top Width und Height, anhand des Winkels
Wenn ich jetzt SetBounds benutze, gibt es wieder eine Endlosschleife.
Das gestaltet sich wirklich schwierig.... hätte ich nicht gedacht.


Irgendwas ist da schief.
SetBounds tut nämlich gar nichts, falls die angegebenen Werte schon den existierenden entsprechen.

Code: Alles auswählen

// aus customlabel.inc
procedure TCustomLabel.SetBounds(aLeft, aTop, aWidth, aHeight: integer);
begin
  if (Left=aLeft) and (Top=aTop) and (Width=aWidth) and (Height=aHeight) then exit;
...

Offenbar ändern sich deine SetBounds Parameter dauernd, sonst wüsste ich nicht, wie es dadurch zur Endlosschleife kommen kann.

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Text Rechteck ermitteln

Beitrag von wp_xyz »

Wie schon gesagt: Berechne die neue Größe in CalculatePreferredSize (override!, inherited belassen, da es die ungedrehte Größe liefert), entferne Calculate, in SetAngle rufe Invalidate auf, entferne DoAutoSize.

Das ist ins Blaue hinein geraten, ich hätte es gerne ausprobiert, aber ich habe keine Lust mir aus deinen Code-Fragmenten eine funktionsfähige Komponente zusammenzustellen.

Antworten