Autor Beitrag
Mathematiker
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 2622
Erhaltene Danke: 1447

Win 7, 8.1, 10
Delphi 5, 7, 10.1
BeitragVerfasst: So 03.11.13 23:37 
Hallo,
zum Thema "Zeilenumbruch" gibt es sehr viele Einträge in der EE, jedoch passt keiner zu meinem Problem.

Ich möchte einen längeren Text mittels Textout auf ein Canvas ausgeben. Der Text ist i.A. breiter als bei der gewählten Schriftart Platz auf der Zeichenfläche ist, d.h. ich muss Zeilenumbrüche einfügen.
Bisher gehe ich so vor, dass ich die Textbreite teste und gegebenenfalls von rechts nach dem Auftreten eines Leerzeichens suche, erneut teste usw. usf.

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
while (not (ziel.canvas.textwidth(s)+bildoffset<=lexbitmap.width-17))
      and (pos(' ',s)<>0do
begin
    rand:=length(s);
    rand2:=rand+1;
    while (s[rand]<>' 'and (rand>1do dec(rand);
    if rand>1 then delete(s,rand,rand2-rand);
end;

Dies funktioniert gut und ich kann auch die Breite des Canvas-Bereiches zur Laufzeit ändern. Der gefundene Teilstring wird ausgegeben und mit dem verbleibenden Rest erneut nach notwendigen Zeilenumbrüchen gesucht. Kein Problem.
Das einzige, was mich stört, ist die Tatsache, dass gerade bei längeren Texten die Ausgabezeit etwas lang werden kann.

Die Idee, den Text im Hintergrund in ein Memo zu kopieren und dort den automatischen Umbruch zu nutzen, scheiterte leider, da ich nicht die notwendige Breite des Memos ermitteln konnte. Memo-Breite = Canvas-Breite stimmte nicht. Warum, weiß ich nicht.
Ein Ersetzen des Canvas durch z.B. Richedit oder die Nutzung von Drawtext usw. geht leider auch nicht, da zusätzlich zum Text noch Abbildungen und Spezialzeichen eingefügt werden sollen. Außerdem werden mitunter innerhalb einer Zeile wortweise die Schriftart, die Farbe, der Stil usw. gewechselt und vor allem Links zu anderen Textseiten eingefügt.

Vom Anfang des Textes zu suchen und zu testen, erschien mir eigentlich logisch. Dabei hatte ich solange nach Leerzeichen gesucht, bis der String zu breit war. Danach ging ich wieder um ein Leerzeichen zurück. Da in den Texten auch kürzere Zeilen mit nur einem Umbruch auftreten, kostete dieses Verfahren in der Summe leider mehr Zeit.

Deshalb meine Frage: Sieht jemand eine Möglichkeit, wie man den ganzen Vorgang beschleunigen könnte, d.h. gibt es eine Idee, die schneller die Zeilenumbrüche ermitteln kann.

Danke für Eure Hilfe
Mathematiker

_________________
Töten im Krieg ist nach meiner Auffassung um nichts besser als gewöhnlicher Mord. Albert Einstein
Martok
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 3661
Erhaltene Danke: 604

Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
BeitragVerfasst: Mo 04.11.13 01:50 
Wie lang ist "etwas lang"?

Die Idee an sich ist schon okay so, macht man auch an anderer Stelle so. Man könnte noch etwas an den String-Operationen spielen (siehe Anhang), aber wenn die Texte sehr lang werden fällt mir auch nur ein, das Layouten vom Zeichnen zu trennen und die Ergebnisse zwischenzuspeichern.
Einloggen, um Attachments anzusehen!
_________________
"The phoenix's price isn't inevitable. It's not part of some deep balance built into the universe. It's just the parts of the game where you haven't figured out yet how to cheat."

Für diesen Beitrag haben gedankt: Mathematiker
WasWeißDennIch
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 653
Erhaltene Danke: 160



BeitragVerfasst: Mo 04.11.13 09:13 
Ich mache so etwas immer mittels DrawText (es gibt AFAIK auch eine TCanvas-Methode dafür, nur fällt mir die gerade nicht ein) mit gesetztem DT_WORDBREAK-Flag, dann übernimmt Windows ggf. die automatischen Zeilenumbrüche.
Horst_H
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1652
Erhaltene Danke: 243

WIN10,PuppyLinux
FreePascal,Lazarus
BeitragVerfasst: Mo 04.11.13 17:39 
Hallo,

ich habe Martoks Version leicht verändert und einen zusätzlichen Druckknopf hinzugefügt.
Ich habe den Text wortweise zu einer Zeile zusammengepackt und nur für das jeweilige nächste Wort die Länge bestimmt.
Damit erspart man sich die Bestimmung textwidth von langen Texten = langsam und zudem braucht man nicht mit copy und delete zu arbeiten.

ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
unit Unit1;

{$IFDEF FPC}
  {$MODE Delphi}
{$ENDIF}

interface

uses
{$IFNDEF FPC}
  Windows,
{$ELSE}
  LCLIntf, LCLType, LMessages,
{$ENDIF}
  Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type

  { TForm1 }

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    CheckBox1: TCheckBox;
    ScrollBar1: TScrollBar;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation
uses StrUtils;

{$IFNDEF FPC}
  {$R *.dfm}
{$ELSE}
  {$R *.lfm}
{$ENDIF}
const
  Vorlage1=
    'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ' +
    'ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo ' +
    'dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.';
  Vorlage = Vorlage1+Vorlage1+Vorlage1+Vorlage1+Vorlage1+Vorlage1+Vorlage1+Vorlage1+
            Vorlage1+Vorlage1+Vorlage1+Vorlage1+Vorlage1+Vorlage1+Vorlage1+Vorlage1;

procedure TForm1.Button1Click(Sender: TObject);
const
  cTrenner = ' ';
  runden = 10;
var
  t1, t2: TDateTime;
  s,sZeile,sWort: string;
  leer, leerneu, MaxBreite,
  lineCnt,txtHeight,
  lenZeile,lenWort,lenTrenner: integer;
  j: integer;
  cB : boolean;

begin
  s:= Vorlage;
  T1 := time;
  txtHeight :=  Canvas.TextHeight('Xg')+1;
  lenTrenner := Canvas.TextWidth(cTrenner);
  MaxBreite:= ScrollBar1.Position;
  Canvas.FillRect(Rect(0,0, scrollBar1.Max+1, Form1.Height));
  //Wegen Lazarus ( unter wine 1.7), weil es aus 1.1 ms -> 7 ms macht :-(
  //was nichts mit der Programmperformance zu tuen haben sollte.
  cB := CheckBox1.Checked;
  IF cB then
    j := 0
  else
    j := runden-1;
For j := j downto 0 do
  begin
  leer:= 0;
  with Canvas do begin
    lineCnt:= 0;
    while (leer < length(s)) do begin                   // für jede Zeile
      sZeile := '';
      lenZeile := 0;
      repeat
        leerneu:= PosEx(' ', s, leer+1);                // naechstes leerzeichen
        if leerneu = 0 then                             // oder Ende wenn keins
          leerneu:= length(s);
        sWort :=Copy(s, leer+1, leerneu-leer-1);
        lenWort := TextWidth(sWort);
        if lenWort+lenZeile > MaxBreite then        // neu wäre zu lang
          break;
        leer:= leerneu;
        sZeile := sZeile+sWort+cTrenner;
        lenZeile:= lenZeile+lenWort+lenTrenner;
      until (leer >= length(s));
      // wenn ende erreicht, ausgeben
      // Zuviel angehaengtes Leerzeichen entfernen
      IF lenZeile > 0 then
        setlength(sZeile,length(sZeile)-length(cTrenner))
      else
        begin
        // -> wenigstens ein wort, also dahinter weitermachen
        sZeile := sWort;
        leer:= leerneu;
        end;
      IF cB then
        TextOut(0, lineCnt * TxtHeight, sZeile); // text ausgeben, neue zeile
      inc(lineCnt);
    end;
    // "Deko"
    MoveTo(MaxBreite, 0);
    LineTo(MaxBreite, Form1.Height);
  end;
end;
  T2 := time;
  IF CheckBox1.Checked then
    Caption:= Format('Breite %4d Zeilen: %d Zeit: %.3fms', [MaxBreite,lineCnt,(t2 - t1) * 86400*1000])
  else
    Caption:= Format('Breite %4d Zeilen: %d  :Ohne Ausgabe Zeit: %.3fms', [MaxBreite,lineCnt,(t2 - t1) * 86400*1000/runden]);
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  s: string;
  leer, leerneu, MaxBreite, lineCnt,txtHeight: integer;
  t1, t2: TDateTime;
begin
  s:= Vorlage;
  T1 := Time;
  txtHeight :=  Canvas.TextHeight('Xg')+1;
  MaxBreite:= ScrollBar1.Position;

  with Canvas do begin
    // "Deko"
    FillRect(Rect(0,0, scrollbar1.max+1, Form1.height));
    MoveTo(MaxBreite, 0);
    LineTo(MaxBreite, Form1.height);
    lineCnt:= 0;
    while (s > ''do begin                                       // für jede Zeile
      leer:= 0;
      repeat
        leerneu:= PosEx(' ', s, leer + 1);                        // nächstes leerzeichen
        if leerneu = 0 then                                       // oder Ende wenn keins
          leerneu:= length(s);
        if TextWidth(Copy(s, 1, leerneu)) > MaxBreite then        // neu wäre zu lang
          break;
        leer:= leerneu;                                           // weiter ab da
      until (leer >= length(s));                                  // wenn ende erreicht, ausgeben

      if leer = 0 then                                            // schon das erste war zu lang?
        leer:= leerneu;                                           // -> wenigstens ein wort
      TextOut(0, lineCnt * txtHeight, Copy(s, 1, leer));                    // text ausgeben, löschen, neue zeile
      Delete(s, 1, leer);
      inc(lineCnt);
    end;
  end;
  T2 := Time;
  Caption:= Format('Breite %4d Zeilen: %d Zeit: %.3fms', [MaxBreite,lineCnt,(t2 - t1) * 86400*1000])
end;

end.

Vielleicht fällt auf, das die Texte je nach Version fast immer unterschiedliche Zeilenzahl haben, da Martoks Version am Ende des Zeilentextes das Leerzeichen belässt ( meine ich zumindest so gesehen zu haben ).
Vielleicht ist unter Delphi ein Leistungszuwachs zu erkennen.

Gruß Horst

Für diesen Beitrag haben gedankt: Mathematiker
Mathematiker Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 2622
Erhaltene Danke: 1447

Win 7, 8.1, 10
Delphi 5, 7, 10.1
BeitragVerfasst: Mo 04.11.13 18:35 
Hallo,
user profile iconMartok hat folgendes geschrieben Zum zitierten Posting springen:
Wie lang ist "etwas lang"?

Das eigentliche Problem ist, dass die jeweils anzuzeigenden Texte zuerst aus einer DLL in eine Stringliste geladen und dort aus einer etwa 200k langen "Textmenge" extrahiert werden, evtl. Bilder geladen werden und dann die Ausgabe auf TCanvas, evtl. mit Zeilenumbruch, erfolgt. Im Allgemeinen dauert das nur etwa 10 ms je Seite, d.h. es ist ziemlich schnell.
Ich musste aber feststellen, dass für einige längere Textpassagen ab etwa 2000 Zeichen Länge die Ermittlung des Zeilenumbruchs den größten Zeitbedarf hat. Dann kann es durchaus auch mal mehr als 0,2 s dauern und das merkt man deutlich. Und es stört mich.
user profile iconMartok hat folgendes geschrieben Zum zitierten Posting springen:
Man könnte noch etwas an den String-Operationen spielen (siehe Anhang), ...

Danke, das ist ein interessanter Ansatz.

user profile iconWasWeißDennIch hat folgendes geschrieben Zum zitierten Posting springen:
Ich mache so etwas immer mittels DrawText ...

Hilft mir leider nicht, wie ich schon oben ausgeführt habe.

user profile iconHorst_H hat folgendes geschrieben Zum zitierten Posting springen:
... ich habe Martoks Version leicht verändert ... Ich habe den Text wortweise zu einer Zeile zusammengepackt und nur für das jeweilige nächste Wort die Länge bestimmt. ...

Sehr schön und es funktioniert perfekt.
Nach dem ersten Test konnte ich für den Zeilenumbruch einen Geschwindigkeitsgewinn von etwa 10% erreichen, bei längeren Texten sogar deutlich mehr. Das ist ja schon etwas.

Während einer langweiligen Mathe-Unterrichtsstunde kam mir heute noch die Idee (das Gehirn arbeitet weiter!), vor der Ausgabe mit
ausblenden Delphi-Quelltext
1:
maxzeichen:=2*(lexbitmap.width-bildoffset) div ziel.textwidth('ie');					

eine ungefähre Anzahl von Zeichen zu ermitteln, die noch in einer Zeile dargestellt werden können. Mal sehen, ob das noch etwas bringt.

Besten Dank an alle für die Hinweise
Mathematiker

_________________
Töten im Krieg ist nach meiner Auffassung um nichts besser als gewöhnlicher Mord. Albert Einstein
Horst_H
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1652
Erhaltene Danke: 243

WIN10,PuppyLinux
FreePascal,Lazarus
BeitragVerfasst: Mo 04.11.13 19:06 
Hallo,

um ein Memo Feld zu benutzen musst Du die richtige Breite einstellen und die Ränder berücksichtigen:
ausblenden Delphi-Quelltext
1:
2:
3:
  with memo1 do
    Width := MaxBreite+Margins.Left+Margins.Right;
  memo1.Lines.add(Vorlage);

Dann wird das Ergebnis auch passend.
Ich habe meine Version angehängt in der das passiert.Ob das nun schneller ist, weiß ich nicht.

Natürlich könnte man mit einer mittleren Buchstabenbreite abschätzen, wo der Umbruch erfolgen muß.
Dabei könnte man durch Verwendung einer zu geringen Abschätzung ( also mit maximaler Breite von 'X' oder 'L' ) , das Vorergebnis weiter nutzen, denn erst 110 und dann 100 Buchstaben zu testen ist langsamer als erst 90 Buchstaben und anschließend noch 10.

Gruß Horst
Edit:
Neue Version. Mit Paintbox und Memo nebeneinander.In beiden sollte der Text, bei ausreichender Breite, gleich aussehen.
Eine kleine Verbessrung: Es wird kein Text mehr in die Paintbox ausgegeben, wenn mehr Text, als dort passend ausgegeben wird.
Einloggen, um Attachments anzusehen!

Für diesen Beitrag haben gedankt: Mathematiker