Autor |
Beitrag |
Mathematiker
Beiträge: 2622
Erhaltene Danke: 1447
Win 7, 8.1, 10
Delphi 5, 7, 10.1
|
Verfasst: 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.
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8:
| while (not (ziel.canvas.textwidth(s)+bildoffset<=lexbitmap.width-17)) and (pos(' ',s)<>0) do begin rand:=length(s); rand2:=rand+1; while (s[rand]<>' ') and (rand>1) do 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
Beiträge: 3661
Erhaltene Danke: 604
Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
|
Verfasst: 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
Beiträge: 653
Erhaltene Danke: 160
|
Verfasst: 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
Beiträge: 1653
Erhaltene Danke: 243
WIN10,PuppyLinux
FreePascal,Lazarus
|
Verfasst: 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.
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 = class(TForm) Button1: TButton; Button2: TButton; CheckBox1: TCheckBox; ScrollBar1: TScrollBar; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); private public 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)); 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 sZeile := ''; lenZeile := 0; repeat leerneu:= PosEx(' ', s, leer+1); if leerneu = 0 then leerneu:= length(s); sWort :=Copy(s, leer+1, leerneu-leer-1); lenWort := TextWidth(sWort); if lenWort+lenZeile > MaxBreite then break; leer:= leerneu; sZeile := sZeile+sWort+cTrenner; lenZeile:= lenZeile+lenWort+lenTrenner; until (leer >= length(s)); IF lenZeile > 0 then setlength(sZeile,length(sZeile)-length(cTrenner)) else begin sZeile := sWort; leer:= leerneu; end; IF cB then TextOut(0, lineCnt * TxtHeight, sZeile); inc(lineCnt); end; 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 FillRect(Rect(0,0, scrollbar1.max+1, Form1.height)); MoveTo(MaxBreite, 0); LineTo(MaxBreite, Form1.height); lineCnt:= 0; while (s > '') do begin leer:= 0; repeat leerneu:= PosEx(' ', s, leer + 1); if leerneu = 0 then leerneu:= length(s); if TextWidth(Copy(s, 1, leerneu)) > MaxBreite then break; leer:= leerneu; until (leer >= length(s)); if leer = 0 then leer:= leerneu; TextOut(0, lineCnt * txtHeight, Copy(s, 1, leer)); 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
Beiträge: 2622
Erhaltene Danke: 1447
Win 7, 8.1, 10
Delphi 5, 7, 10.1
|
Verfasst: Mo 04.11.13 18:35
Hallo,
Martok hat folgendes geschrieben : | 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.
Martok hat folgendes geschrieben : | Man könnte noch etwas an den String-Operationen spielen (siehe Anhang), ... |
Danke, das ist ein interessanter Ansatz.
WasWeißDennIch hat folgendes geschrieben : | Ich mache so etwas immer mittels DrawText ... |
Hilft mir leider nicht, wie ich schon oben ausgeführt habe.
Horst_H hat folgendes geschrieben : | ... 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
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
Beiträge: 1653
Erhaltene Danke: 243
WIN10,PuppyLinux
FreePascal,Lazarus
|
Verfasst: 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:
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
|
|
|