Wie hab ich's nun gemacht:
Global verwende ich 3x TRichEdit - RE_SRC hält den Source (aus zu ladender Datei), RE_TMP ist für temporäre Suche und RE_PAGE bekommt den zu druckenden RTF-Inhalt.
Aus RE_SRC hole ich mir den Plain-Text via Stream. Darin suche ich erst einmal das Key-Wort '\page', um die Pos. des 1. Seitenumbruch im Source zu suchen.
Kleiner Nebeneffekt: Der TRichEdit-Parser hat ne ganze Menge "Müll" aus dem Text entfernt - aber gewünschte '\page' oder '\pagebb' sind drin geblieben !
Nun loope ich durch RE_SRC.Lines[], nehme den Text daraus und suche wieder im Source von RE_SRC nach dem Anfang des Textes.
Ist die Text-Pos. > als die Page-Pos., gehört die aktuelle Line[] auf die nächste Seite.
Dann wieder neu nach '\page' suchen und weiter mit der nächsten Zeile .... bis alle Zeilen durchlaufen.
Funzt sehr gut, ABER...
... nur solange der RTF-Text keine Umlaute (z.B. ein "ä" ist dann \'e4) oder "RTF-Sonderzeichen" enthält (z.B. '...' ist ein "ellipsis" - \'85).
Im formatierten Text sieht man das logo nicht, aber ein Textvergleich mit dem Source via Pos/PosEx ergibt -1 weil keine Übereinstimmung.
Also ein bischen Tricky:
Ich nehme also den Text und erzeuge damit im RE_TMP eine Zeile. Diese lese ich dann aus dem Stream wieder aus und habe somit wieder die ursprüngliche Formatierung, womit ich dann vergleichen kann.
2. Problem:
Enthält der Text nun auch noch Formatierungen wie Farbe, andere Schriftgröße, Bold, etc., dann stehen noch mehr Formatierungszeichen im Source, die leider nicht mit vorgenanntem Trick wieder auftauchen.
Ich habe dazu bisher noch keine Lösung gefunden - vielleicht hat hier ja jemand eine Idee !?
Hier die beiden proceduren:
Delphi-Quellcode:
// -----------------------------------------------------------------------------
procedure TForm1.RTF_Parser(Datei : string);
var
SS : TStringStream;
s, s2 : string;
i, PosStr, Ppage : longint;
LineFrom, LineTo : integer;
begin
Memo.Clear;
ClearPagelList;
RE_SRC.Clear;
RE_TMP.Clear;
RE_PAGE.Clear;
RE_SRC.Lines.LoadFromFile(Datei);
if (RE_SRC.Lines.Count < 1) then
begin
Memo.Lines.Add('... nothing to print !');
exit;
end;
SS := TStringStream.Create('');
RE_SRC.Lines.SaveToStream(SS);
SS.Position := 0;
Memo.Lines.Add('Num of Lines to print: ' + inttostr(RE_SRC.Lines.Count));
PosStr := 1;
Ppage := PosEx('\page', SS.DataString, PosStr); // read also '\pagebb'
LineFrom := 0;
LineTo := 0;
for I := 0 to RE_SRC.Lines.Count - 1 do
begin
s := RE_SRC.Lines[i];
if (S = '') then
continue;
s2 := Get_RTF_plain(s);
PosStr := Pos(s2, (SS.DataString));
if (Ppage > 0) AND (PosStr > Ppage) then
begin
LineTo := i - 1;
AddPage2List(LineFrom, LineTo); // Add Page to list:
LineFrom := LineTo + 1;
Ppage := PosEx('\page', SS.DataString, PosStr); // read also '\pagebb'
end;
end;
AddPage2List(LineFrom, RE_SRC.Lines.Count - 1);
SS.Free;
end;
// -----------------------------------------------------------------------------
function TForm1.Get_RTF_plain(s : string) : string;
var
SS : TStringStream;
p1, p2 : integer;
begin
SS := TStringStream.Create('');
RE_tmp.Clear;
RE_tmp.SelText := s;
RE_tmp.Lines.SaveToStream(SS);
p1 := PosEx('\pard', (SS.DataString), 1);
p1 := PosEx(' ', (SS.DataString), p1);
p2 := PosEx('\par', (SS.DataString), p1 + 1);
ss.Position := p1;
result := ss.ReadString(p2 - p1 - 1);
SS.Free;
end;
Zum 'merken' welche Seiten welche Zeilen enthalten, benutze ich ein TList, wo ich an jedes neue Item (die Seite) einen Record mit den Zeilen From-To einhänge.
Delphi-Quellcode:
// -----------------------------------------------------------------------------
// http://delphi.xcjc.net/viewthread.php?tid=43702
procedure TForm1.AddPage2List(LineFrom, LineTo : integer);
var
Item : PRTFPageData;
begin
// PageNum is Itemindex + 1
New(Item);
Item.LFrom := LineFrom;
Item.LTo := LineTo;
FPageList.Add(Item);
cbb_SelPage.Items.Add('- Seite ' + inttostr(FPageList.Count));
Memo.Lines.Add('!!! Page added: ' + inttostr(FPageList.Count) +
' (Lines ' + inttostr(LineFrom) +
' - ' + inttostr(LineTo) + ')');
end;
Zum Drucken des jeweilige Seiteninhalts kopiere ich zunächst das RE_SRC via Stream in RE_PAGE, damit die Formatierungen bleiben.
Um nur den gewünschten Inhalt zu bekommen, lösche ich alle nicht gewünsvhten Zeile einfach aus RE_Page heraus.
ACHTUNG: Ich lese die Zeilen rückwärts mit for ... downto, weil sonst (von vorne gelesen) die Zeilennummern nach Löschen nicht mehr passen.
Delphi-Quellcode:
// -----------------------------------------------------------------------------
// RE_SRC is the RTF-Source by loading !!!
procedure TForm1.PrintRTF(PageFrom, PageTo : integer);
var
i, LF, LT : integer;
begin
if (PageFrom < 0) then
begin // => Print all
Memo.Lines.Add('... now Printing all pages');
SelectLines(0, RE_SRC.Lines.Count - 1);
// call your favourite RTF-printing-service with RE_SRC !!!
end
else
for i := PageFrom to PageTo do
begin
Memo.Lines.Add('... now Printing page: ' + inttostr(i));
LF := PRTFPageData(FPageList[i - 1]).LFrom;
LT := PRTFPageData(FPageList[i - 1]).LTo;
SelectLines(LF, LT);
// call your favourite RTF-printing-service with RE_PAGE....
end;
end;
// -----------------------------------------------------------------------------
// RE_SRC is the RTF-Source by loading !!!
// New RTF-page remains in RE_PAGE !!!
// http://delphi.xcjc.net/viewthread.php?tid=43702
procedure TForm1.SelectLines(LineFrom, LineTo : integer);
var
i : integer;
MS : TMemoryStream;
begin
if (LineTo < LineFrom) then
begin
Memo.Lines.Add(' ERROR: Invalid Lines to print');
exit;
end;
Memo.Lines.Add('--- SelectLinesFromSource:' +
' - Max: ' + inttostr(RE_SRC.Lines.count) +
' - from '+ inttostr(LineFrom + 1) +
' to ' + inttostr(LineTo + 1));
// 1st: copy RE to RE_PAGE
MS := TMemoryStream.Create;
try
RE_SRC.Lines.SaveToStream(MS);
MS.Position := 0;
RE_PAGE.Lines.LoadFromStream(MS) ;
finally
MS.Free;
end;
// 2nd: Loop through all RE_SRC-lines and delete unwanted lines
// !!! walking BACKWARDS through list of lines !!!
for i := RE_PAGE.Lines.Count - 1 downto 0do
begin
if (i < LineFrom) then RE_PAGE.Lines.Delete(i);
if (i > LineTo) then RE_PAGE.Lines.Delete(i);
end;
// call your favourite RTF-printing-service with RE_PAGE....
end;
Das läuft hier schon sehr sauber - muss aber logo betreff 'Eleganz' noch ein bischen überarbeitet werden.
Schön wäre es natürlich, wenn auch formatierter Text zum Seitenumbruch richtig erkannt würde.
Ich hab das panze Test-Proggie mal eingepackt. Ich hoffe das ist ausreichend selbsterklärend.
Kompiliert mit D2009 unter Win 8.1 x64. Es wird nur Standard verwendet.
Dazu noch meine 3 Test-RTFs, mit LibreOffice erzeugt.
NACHTRAG:
Die procedure Get_RTF_plain ist auch eher etwas "Brute Force", ohne den Source hier nun in Blöcke zu zerlegen und umständlich im Detail zu parsen. Ich habe bisher noch nirgends was einfaches gefunden, wie man konkret in einem RTF den Anfang einer Textzeile erkennt.
Angeblich beginnt die immer mit einem " " - Angaben zu Zeichensätzen u. a. aber auch ...