lazarus-ccr/components/richmemo/richmemortf.pas
2020-01-22 20:08:26 +00:00

980 lines
27 KiB
ObjectPascal
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

unit RichMemoRTF;
interface
{$mode objfpc}{$h+}
uses
Classes, SysUtils, LCLProc, LCLIntf, LConvEncoding, Graphics,
RichMemo, RTFParsPre211;
function MVCParserLoadStream(ARich: TCustomRichMemo; Source: TStream): Boolean;
procedure RegisterRTFLoader;
type
TEncConvProc = function (const s: string): string;
//todo: rewrite! it's not language based but fontchar-set based
procedure LangConvAdd(lang: Integer; convproc: TEncConvProc);
function LangConvGet(lang: Integer; var convproc: TEncConvProc): Boolean;
type
TSaveParams = record // reserved
start : Integer; // the first character for the extract
len : Integer; // the number of characters to extract
end;
// the function depends on GetStyleRange and to be implemented properly
// if GetStyleRange, GetParaMetric, GetParaAlignment is not working properly
// the resulting RTF would not contain any styles or the text styles would be wrong
procedure IntSaveStream(ARich: TcustomRichMemo; SaveParams: TSaveParams; Dst: TStream);
function SaveStream(ARich: TCustomRichMemo; Dst: TStream): Boolean;
procedure RegisterRTFSaver;
implementation
uses LazUTF8;
var
LangConvTable : array of record lang: integer; proc: TEncConvProc end;
LangCount : Integer = 0;
procedure LangConvAdd(lang: Integer; convproc: TEncConvProc);
var
i : integer;
begin
for i:=0 to LangCount-1 do
if LangConvTable[i].lang=lang then begin
LangConvTable[i].proc:=convproc;
Exit;
end;
if LangCount=length(LangConvTable) then begin
if LangCount=0 then SetLength(LangConvTable, 64)
else SetLength(LangConvTable, LangCount*2);
end;
LangConvTable[LangCount].lang:=lang;
LangConvTable[LangCount].proc:=convproc;
inc(LangCount);
end;
type
{ TRTFMemoParser }
{ TRTFParams }
TRTFParams = class(TObject)
public
fnt : TFontParams;
pm : TParaMetric;
pa : TParaAlignment;
fnum : Integer; // font index in the font table
prev : TRTFParams;
tabs : TTabStopList;
constructor Create(aprev: TRTFParams);
procedure ResetDefault;
procedure AddTab(AOffset: double; ta: TTabAlignment);
end;
TRTFMemoParser = class(TRTFParser)
private
txtbuf : String; // keep it UTF8 encoded!
txtlen : Integer;
HLFromCTable : Boolean;
prm : TRTFParams;
lang : Integer;
langproc : TEncConvProc;
deflang : integer;
skipNextCh: Boolean; // For a Unicode escape the control word \u is used,
// followed by a 16-bit signed decimal integer giving
// the Unicode UTF-16 code unit number. For the benefit
// of programs without Unicode support, this must be followed
// by the nearest representation of this character in the specified code page.
// For example, \u1576? would give the Arabic letter bāʼ ب, specifying that
// older programs which do not have Unicode support should render it as a
// question mark instead.
procedure AddText(const atext: string);
protected
procedure classUnk;
procedure classText;
procedure classControl;
procedure classGroup;
procedure classEof;
procedure doChangePara(aminor, aparam: Integer);
procedure doDestination(aminor, aparam: Integer);
procedure doSpecialChar;
procedure doChangeCharAttr(aminor, aparam: Integer);
procedure SetLanguage(AlangCode: integer);
function DefaultTextColor: TColor;
procedure PushText;
public
Memo : TCustomRichMemo;
constructor Create(AMemo: TCustomRichMemo; AStream: TStream);
destructor Destroy; override;
procedure StartReading;
end;
function LangConvGet(lang: Integer; var convproc: TEncConvProc): Boolean;
var
i : integer;
begin
for i:=0 to LangCount-1 do
if LangConvTable[i].lang=lang then begin
convproc:=LangConvTable[i].proc;
Result:=true;
Exit;
end;
Result:=false;
end;
procedure LangConvInit;
begin
LangConvAdd(1052, @CP1250ToUTF8); // Albanian
LangConvAdd(1050, @CP1250ToUTF8); // Croatian
LangConvAdd(1029, @CP1250ToUTF8); // Czech
LangConvAdd(1038, @CP1250ToUTF8); // Hungarian
LangConvAdd(1045, @CP1250ToUTF8); // Polish
LangConvAdd(1048, @CP1250ToUTF8); // Romanian
LangConvAdd(2074, @CP1250ToUTF8); // Serbian - Latin
LangConvAdd(1051, @CP1250ToUTF8); // Slovak
LangConvAdd(1060, @CP1250ToUTF8); // Slovenian
LangConvAdd(2092, @CP1251ToUTF8); // Azeri - Cyrillic
LangConvAdd(1059, @CP1251ToUTF8); // Belarusian
LangConvAdd(1026, @CP1251ToUTF8); // Bulgarian
LangConvAdd(1071, @CP1251ToUTF8); // FYRO Macedonia
LangConvAdd(1087, @CP1251ToUTF8); // Kazakh
LangConvAdd(1088, @CP1251ToUTF8); // Kyrgyz - Cyrillic
LangConvAdd(1104, @CP1251ToUTF8); // Mongolian
LangConvAdd(1049, @CP1251ToUTF8); // Russian
LangConvAdd(3098, @CP1251ToUTF8); // Serbian - Cyrillic
LangConvAdd(1092, @CP1251ToUTF8); // Tatar
LangConvAdd(1058, @CP1251ToUTF8); // Ukrainian
LangConvAdd(2115, @CP1251ToUTF8); // Uzbek - Cyrillic
LangConvAdd(1078, @CP1252ToUTF8); // Afrikaans
LangConvAdd(1069, @CP1252ToUTF8); // Basque
LangConvAdd(1027, @CP1252ToUTF8); // Catalan
LangConvAdd(1030, @CP1252ToUTF8); // Danish
LangConvAdd(2067, @CP1252ToUTF8); // Dutch - Belgium
LangConvAdd(1043, @CP1252ToUTF8); // Dutch - Netherlands
LangConvAdd(3081, @CP1252ToUTF8); // English - Australia
LangConvAdd(10249,@CP1252ToUTF8); // English - Belize
LangConvAdd(4105, @CP1252ToUTF8); // English - Canada
LangConvAdd(9225, @CP1252ToUTF8); // English - Caribbean
LangConvAdd(2057, @CP1252ToUTF8); // English - Great Britain
LangConvAdd(6153, @CP1252ToUTF8); // English - Ireland
LangConvAdd(8201, @CP1252ToUTF8); // English - Jamaica
LangConvAdd(5129, @CP1252ToUTF8); // English - New Zealand
LangConvAdd(13321,@CP1252ToUTF8); // English - Phillippines
LangConvAdd(7177, @CP1252ToUTF8); // English - Southern Africa
LangConvAdd(11273,@CP1252ToUTF8); // English - Trinidad
LangConvAdd(1033, @CP1252ToUTF8); // English - United States
LangConvAdd(12297,@CP1252ToUTF8); // English - Zimbabwe
LangConvAdd(1080, @CP1252ToUTF8); // Faroese
LangConvAdd(1035, @CP1252ToUTF8); // Finnish
LangConvAdd(2060, @CP1252ToUTF8); // French - Belgium
LangConvAdd(3084, @CP1252ToUTF8); // French - Canada
LangConvAdd(1036, @CP1252ToUTF8); // French - France
LangConvAdd(5132, @CP1252ToUTF8); // French - Luxembourg
LangConvAdd(6156, @CP1252ToUTF8); // French - Monaco
LangConvAdd(4108, @CP1252ToUTF8); // French - Switzerland
LangConvAdd(1110, @CP1252ToUTF8); // Galician
LangConvAdd(3079, @CP1252ToUTF8); // German - Austria
LangConvAdd(1031, @CP1252ToUTF8); // German - Germany
LangConvAdd(5127, @CP1252ToUTF8); // German - Liechtenstein
LangConvAdd(4103, @CP1252ToUTF8); // German - Luxembourg
LangConvAdd(2055, @CP1252ToUTF8); // German - Switzerland
LangConvAdd(1039, @CP1252ToUTF8); // Icelandic
LangConvAdd(1057, @CP1252ToUTF8); // Indonesian
LangConvAdd(1040, @CP1252ToUTF8); // Italian - Italy
LangConvAdd(2064, @CP1252ToUTF8); // Italian - Switzerland
LangConvAdd(2110, @CP1252ToUTF8); // Malay - Brunei
LangConvAdd(1086, @CP1252ToUTF8); // Malay - Malaysia
LangConvAdd(1044, @CP1252ToUTF8); // Norwegian - Bokml
LangConvAdd(2068, @CP1252ToUTF8); // Norwegian - Nynorsk
LangConvAdd(1046, @CP1252ToUTF8); // Portuguese - Brazil
LangConvAdd(2070, @CP1252ToUTF8); // Portuguese - Portugal
LangConvAdd(1274, @CP1252ToUTF8); // Spanish - Argentina
LangConvAdd(16394,@CP1252ToUTF8); // Spanish - Bolivia
LangConvAdd(13322,@CP1252ToUTF8); // Spanish - Chile
LangConvAdd(9226, @CP1252ToUTF8); // Spanish - Colombia
LangConvAdd(5130, @CP1252ToUTF8); // Spanish - Costa Rica
LangConvAdd(7178, @CP1252ToUTF8); // Spanish - Dominican Republic
LangConvAdd(12298,@CP1252ToUTF8); // Spanish - Ecuador
LangConvAdd(17418,@CP1252ToUTF8); // Spanish - El Salvador
LangConvAdd(4106, @CP1252ToUTF8); // Spanish - Guatemala
LangConvAdd(18442,@CP1252ToUTF8); // Spanish - Honduras
LangConvAdd(2058, @CP1252ToUTF8); // Spanish - Mexico
LangConvAdd(19466,@CP1252ToUTF8); // Spanish - Nicaragua
LangConvAdd(6154, @CP1252ToUTF8); // Spanish - Panama
LangConvAdd(15370,@CP1252ToUTF8); // Spanish - Paraguay
LangConvAdd(10250,@CP1252ToUTF8); // Spanish - Peru
LangConvAdd(20490,@CP1252ToUTF8); // Spanish - Puerto Rico
LangConvAdd(1034, @CP1252ToUTF8); // Spanish - Spain (Traditional)
LangConvAdd(14346,@CP1252ToUTF8); // Spanish - Uruguay
LangConvAdd(8202, @CP1252ToUTF8); // Spanish - Venezuela
LangConvAdd(1089, @CP1252ToUTF8); // Swahili
LangConvAdd(2077, @CP1252ToUTF8); // Swedish - Finland
LangConvAdd(1053, @CP1252ToUTF8); // Swedish - Sweden
LangConvAdd(1032, @CP1253ToUTF8); // greek
LangConvAdd(1068, @CP1254ToUTF8); // Azeri - Latin
LangConvAdd(1055, @CP1254ToUTF8); // turkish
LangConvAdd(1091, @CP1254ToUTF8); // Uzbek - Latin
LangConvAdd(1037, @CP1255ToUTF8); // hebrew
LangConvAdd(5121, @CP1256ToUTF8); // Arabic - Algeria
LangConvAdd(15361,@CP1256ToUTF8); // Arabic - Bahrain
LangConvAdd(3073, @CP1256ToUTF8); // Arabic - Egypt
LangConvAdd(2049, @CP1256ToUTF8); // Arabic - Iraq
LangConvAdd(11265,@CP1256ToUTF8); // Arabic - Jordan
LangConvAdd(13313,@CP1256ToUTF8); // Arabic - Kuwait
LangConvAdd(12289,@CP1256ToUTF8); // Arabic - Lebanon
LangConvAdd(4097, @CP1256ToUTF8); // Arabic - Libya
LangConvAdd(6145, @CP1256ToUTF8); // Arabic - Morocco
LangConvAdd(8193, @CP1256ToUTF8); // Arabic - Oman
LangConvAdd(16385,@CP1256ToUTF8); // Arabic - Qatar
LangConvAdd(1025, @CP1256ToUTF8); // Arabic - Saudi Arabia
LangConvAdd(10241,@CP1256ToUTF8); // Arabic - Syria
LangConvAdd(7169, @CP1256ToUTF8); // Arabic - Tunisia
LangConvAdd(14337,@CP1256ToUTF8); // Arabic - United Arab Emirates
LangConvAdd(9217, @CP1256ToUTF8); // Arabic - Yemen
LangConvAdd(1065, @CP1256ToUTF8); // Farsi - Persian
LangConvAdd(1056, @CP1256ToUTF8); // Urdu
LangConvAdd(1061, @CP1257ToUTF8); // Estonian
LangConvAdd(1062, @CP1257ToUTF8); // Latvian
LangConvAdd(1063, @CP1257ToUTF8); // Lithuanian
LangConvAdd(1066, @CP1258ToUTF8); // vietnam
end;
{ TRTFParams }
constructor TRTFParams.Create(aprev: TRTFParams);
begin
prev:=aprev;
if Assigned(prev) then begin
fnt:=prev.fnt;
pm:=prev.pm;
pa:=prev.pa;
fnum:=prev.fnum;
end else begin
InitFontParams(fnt);
InitParaMetric(pm)
end;
end;
procedure TRTFParams.ResetDefault;
begin
// default values are taken from RTF specs
// see section "Paragraph Formatting Properties"
pa:=paLeft;
pm.FirstLine:=0;
pm.HeadIndent:=0;
pm.TailIndent:=0;
pm.SpaceBefore:=0;
pm.SpaceAfter:=0;
pm.LineSpacing:=0;
tabs.Count:=0;
end;
procedure TRTFParams.AddTab(AOffset: double; ta: TTabAlignment);
begin
if tabs.Count=length(tabs.Tabs) then begin
if tabs.Count=0 then SetLength(tabs.Tabs, 4)
else SetLength(tabs.Tabs, tabs.Count*2);
end;
tabs.Tabs[tabs.Count].Offset:=AOffset;
tabs.Tabs[tabs.Count].Align:=ta;
inc(tabs.Count);
end;
{ TRTFMemoParserr }
procedure TRTFMemoParser.AddText(const atext: string);
var
nl : Integer;
l : Integer;
begin
nl:=txtlen+length(atext);
if nl>length(txtbuf) then begin
l:=length(txtbuf);
while l<nl do
if l=0 then l:=256
else l:=l*2;
SetLength(txtbuf, l);
end;
Move(atext[1], txtbuf[txtlen+1], length(atext));
inc(txtlen, length(atext));
end;
procedure TRTFMemoParser.classUnk;
var
txt : string;
ws : UnicodeString;
begin
if not Assigned(prm) then exit;
txt:=GetRtfText;
if (txt='\object') then begin
SkipGroup;
Exit;
end;
if (length(txt)>2) and (txt[1]='\') and (txt[2]='u') and (txt[3] in ['0'..'9']) then begin
SetLength(Ws,1);
ws[1]:=UnicodeChar(rtfParam);
AddText( UTF8Encode(ws) );
skipNextCh:=true;
end;
end;
function CharToByte(const ch: AnsiChar): Byte;
begin
Result:=0;
if ch in ['0'..'9'] then Result:=byte(ch)-byte('0')
else if ch in ['a'..'f'] then Result:=byte(ch)-byte('a')+10
else if ch in ['A'..'F'] then Result:=byte(ch)-byte('A')+10
end;
function RTFCharToByte(const s: string): byte; inline;
begin
// \'hh A hexadecimal value, based on the specified character set (may be used to identify 8-bit values).
Result:=(CharToByte(s[3]) shl 4) or (CharToByte(s[4]));
end;
procedure TRTFMemoParser.classText;
var
txt : string;
bt : Char;
begin
if not Assigned(prm) then exit;
if skipNextCh then begin
skipNextCh:=false;
Exit;
end;
txt:=Self.GetRtfText;
if (length(txt)=4) and (txt[1]='\') and (txt[2]=#39) then begin
if Assigned(langproc) then begin
bt:=char(RTFCharToByte(txt));
AddText( langproc(bt) );
end;
end else if (length(txt)=2) and (txt[1]='\') and (txt[2] in ['\','{','}']) then begin
AddText(txt[2]);
end else begin
AddText(txt);
end;
end;
procedure TRTFMemoParser.classControl;
begin
if not Assigned(prm) then exit;
if txtlen>0 then begin
PushText;
end;
//writeln('ctrl: ', rtfClass,' ', rtfMajor, ' ', Self.GetRtfText, ' ',rtfMinor,' ', rtfParam);
case rtfMajor of
rtfDestination: doDestination(rtfMinor, rtfParam);
rtfSpecialChar: doSpecialChar;
rtfCharAttr: doChangeCharAttr(rtfMinor, rtfParam);
rtfParAttr: doChangePara(rtfMinor, rtfParam);
end;
end;
procedure TRTFMemoParser.classGroup;
var
t : TRTFParams;
begin
if not Assigned(prm) then exit;
case rtfMajor of
rtfBeginGroup: begin
t:=TRTFParams.Create(prm);
prm:=t;
end;
rtfEndGroup: begin
if Assigned(prm) then begin
t:=prm.prev;
prm.Free;
prm:=t;
end;
end;
end;
end;
procedure TRTFMemoParser.classEof;
begin
PushText;
end;
procedure TRTFMemoParser.doDestination(aminor, aparam: Integer);
begin
case aminor of
rtfDefaultLanguage:
deflang:=aparam;
end;
end;
procedure TRTFMemoParser.doChangePara(aminor, aparam: Integer);
const
TabAl : array [rtfTabPos..rtfTabDecimal] of TTabAlignment = (
tabLeft, tabRight, tabCenter, tabDecimal);
begin
case aminor of
rtfParDef: prm.ResetDefault; // reset clear formatting
rtfQuadLeft: prm.pa:=paLeft;
rtfQuadRight: prm.pa:=paRight;
rtfQuadJust: prm.pa:=paJustify;
rtfQuadCenter: prm.pa:=paCenter;
rtfFirstIndent: begin
prm.pm.FirstLine:=aparam / 20;
end;
rtfLeftIndent: begin
prm.pm.HeadIndent:=aparam / 20;
end;
rtfRightIndent: prm.pm.TailIndent := aparam / 20;
rtfSpaceBefore: prm.pm.SpaceBefore := aparam / 20;
rtfSpaceAfter: prm.pm.SpaceAfter := aparam / 20;
rtfSpaceBetween: prm.pm.LineSpacing := aparam / 200;
// \slN - surprise! the "line spacing" is actually a multiplier based on the FONT size, not linesize
// where linesize = fontsize * 1.2
rtfLanguage: begin
SetLanguage(rtfParam);
end;
rtfTabPos,//; rtfKstr : 'tx'; rtfkHash : 0),
rtfTabRight, // rtfKstr : 'tqr'; rtfkHash : 0),
rtfTabCenter, //; rtfKstr : 'tqc'; rtfkHash : 0),
rtfTabDecimal: //; rtfKstr : 'tqdec'; rtfkHash : 0),
prm.AddTab(aparam / 20, TabAl[aminor]);
end;
end;
procedure TRTFMemoParser.doSpecialChar;
const
{$ifdef MSWINDOWS}
CharPara = #13#10;
{$else}
CharPara = #10;
{$endif}
CharTab = #9;
CharLine = #13;
begin
case rtfMinor of
rtfOptDest: SkipGroup;
rtfLine: AddText(CharLine);
rtfPar: begin
AddText(CharPara);
if deflang<>0 then
SetLanguage(deflang);
end;
rtfTab: AddText(CharTab);
end;
end;
procedure TRTFMemoParser.doChangeCharAttr(aminor, aparam: Integer);
var
p : PRTFColor;
const
HColor : array [1..16] of TColor = (
clBlack
,clBlue
,clAqua // Cyan
,clLime // Green
,clFuchsia //Magenta
,clRed
,clYellow
,clGray // unused!
,clNavy // DarkBlue
,clTeal // DarkCyan
,clGreen // DarkGreen
,clPurple // clDarkMagenta
,clMaroon // clDarkRed
,clOlive // clDarkYellow
,clGray //clDarkGray
,clSilver //clLightGray
);
begin
case aminor of
rtfPlain: prm.fnt.Style:=[];
rtfBold: if aparam=0 then Exclude(prm.fnt.Style,fsBold) else Include(prm.fnt.Style, fsBold);
rtfItalic: if aparam=0 then Exclude(prm.fnt.Style,fsItalic) else Include(prm.fnt.Style, fsItalic);
rtfStrikeThru: if aparam=0 then Exclude(prm.fnt.Style,fsStrikeOut) else Include(prm.fnt.Style, fsStrikeOut);
rtfFontNum: prm.fnum:=aparam;
rtfFontSize: prm.fnt.Size:=round(aparam/2);
rtfUnderline: if aparam=0 then Exclude(prm.fnt.Style,fsUnderline) else Include(prm.fnt.Style, fsUnderline);
rtfNoUnderline: Exclude(prm.fnt.Style, fsUnderline);
rtfSuperScript: prm.fnt.VScriptPos:=vpSuperscript;
rtfSubScript : prm.fnt.VScriptPos:=vpSubScript;
rtfNoSuperSub : prm.fnt.VScriptPos:=vpNormal;
rtfHighlight: begin
prm.fnt.HasBkClr := (aparam>0) and (aparam<=high(HColor));
if prm.fnt.HasBkClr then begin
if HLFromCTable then prm.fnt.BkColor:=HColor[aparam]
else begin
p:=Colors[aparam];
if Assigned(p) then prm.fnt.BkColor:=RGBToColor(p^.rtfCRed, p^.rtfCGreen, p^.rtfCBlue)
// fallback?
else prm.fnt.BkColor:=HColor[aparam];
end;
end;
end;
rtfForeColor: begin
if rtfParam<>0 then p:=Colors[rtfParam]
else p:=nil;
if not Assigned(p) then
prm.fnt.Color:=DefaultTextColor
else
prm.fnt.Color:=RGBToColor(p^.rtfCRed, p^.rtfCGreen, p^.rtfCBlue);
end;
end;
end;
procedure TRTFMemoParser.SetLanguage(AlangCode: integer);
begin
lang:=AlangCode;
langproc:=nil;
LangConvGet(lang, langproc);
end;
function TRTFMemoParser.DefaultTextColor:TColor;
begin
Result:=ColorToRGB(Memo.Font.Color);
end;
procedure TRTFMemoParser.PushText;
var
len : Integer;
pf : PRTFFONT;
selst : Integer;
b : string;
begin
if not Assigned(prm) then exit;
if txtlen=0 then Exit;
b:=Copy(txtbuf, 1, txtlen);
len:=UTF8Length(b);
txtlen:=0;
txtbuf:='';
if len=0 then Exit;
Memo.SelStart:=MaxInt;
selst:=Memo.SelStart;
// in order to get the start selection, we need to switch to the last character
// and then get the value. SelStart doesn't match GetTextLen, since
// "StartSel" is based on number of visible characters (i.e. line break is 1 character)
// while GetTextLen is based on number of actual string characters
// selst:=Memo.GetTextLen;
Memo.SelStart:=selst;
Memo.SelLength:=0;
Memo.SelText:=b;
if Assigned(prm) then begin
prm.pm.FirstLine:=prm.pm.HeadIndent+prm.pm.FirstLine;
Memo.SetParaMetric(selst, 1, prm.pm );
prm.pm.FirstLine:=prm.pm.FirstLine-prm.pm.HeadIndent;
Memo.SetParaAlignment(selst, 1, prm.pa );
if prm.tabs.Count>0 then
Memo.SetParaTabs(selst, 1, prm.tabs);
end;
// Memo.GetTextAttributes(selst, font);
pf:=Fonts[prm.fnum];
if Assigned(pf) then prm.fnt.Name:=pf^.rtfFName;
//prm.fnt.Size:=round(fsz);
//prm.fnt.Style:=fst;
//prm.fnt.Color:=ColorToRGB(fColor);
//prm.fnt.HasBkClr:=hasbk;
//prm.fnt.BkColor:=bcolor;
Memo.SetTextAttributes(selst, len, prm.fnt);
end;
constructor TRTFMemoParser.Create(AMemo:TCustomRichMemo;AStream:TStream);
begin
inherited Create(AStream);
Memo:=AMemo;
ClassCallBacks[rtfText]:=@classText;
ClassCallBacks[rtfControl]:=@classControl;
ClassCallBacks[rtfGroup]:=@classGroup;
ClassCallBacks[rtfUnknown]:=@classUnk;
ClassCallBacks[rtfEof]:=@classEof;
end;
destructor TRTFMemoParser.Destroy;
var
t: TRTFParams;
begin
// cleanup
while Assigned(prm) do begin
t:=prm;
prm:=prm.prev;
t.Free;
end;
inherited Destroy;
end;
procedure TRTFMemoParser.StartReading;
var
t : TRTFParams;
begin
Memo.Lines.BeginUpdate;
try
prm:=TRTFParams.Create(nil);
prm.fnt.Size:=12; //\fsN Font size in half-points (the default is 24).
prm.fnum:=0;
prm.ResetDefault;
inherited StartReading;
PushText;
// clear the stack, if overflow
while Assigned(prm) do begin
t:=prm.prev;
prm.Free;
prm:=t;
end;
Memo.SelStart:=0;
Memo.SelLength:=0;
finally
Memo.Lines.EndUpdate;
end;
end;
function MVCParserLoadStream(ARich: TCustomRichMemo; Source: TStream): Boolean;
var
p : TRTFMemoParser;
begin
Result:=Assigned(ARich) and Assigned(Source);
if not Result then Exit;
p:=TRTFMemoParser.Create(ARich, Source);
try
p.StartReading;
finally
p.Free;
end;
Result:=True;
end;
procedure RegisterRTFLoader;
begin
RTFLoadStream:=@MVCParserLoadStream;
LangConvInit;
end;
function SaveStream(ARich: TcustomRichMemo; Dst: TStream): Boolean;
var
p : TSaveParams;
begin
FillChar(p, sizeof(p), 0);
p.start:=-1;
p.len:=-1;
IntSaveStream(ARich, p, Dst);
Result:=True;
end;
procedure RegisterRTFSaver;
begin
RTFSaveStream:=@SaveStream;
end;
type
TStyleRange = class(TObject)
font : TFontParams;
fontId : Integer; // assigned font ID
colorId : Integer;
textStart : Integer;
textLength : Integer;
next : TStyleRange;
end;
procedure FreeStyleList(var root: TStyleRange);
var
t: TStyleRange;
begin
while Assigned(root) do begin
t:=root.next;
root.Free;
root:=t;
end;
end;
procedure PrepareFontTable(styleslist: TStyleRange; afontTable: TStringList);
var
rng : TStyleRange;
i : integer;
begin
rng:=styleslist;
while Assigned(rng) do begin
i:=afontTable.IndexOf(rng.font.Name);
if i<0 then
i:=afontTable.Add(rng.font.Name);
rng.fontId:=i;
rng:=rng.next;
end;
// {\f0\fswiss\fcharset0 Arial;}
end;
function ColorToRtfText(const cl: TColor): string;
var
r: integer;
begin
r:=ColorToRGB(cl);
Result:=
'\red'+IntToStR( byte( (r and clRed) shr 0) )
+'\green'+IntToStR( byte( (r and clLime) shr 8) )
+'\blue'+IntToStR( byte( (r and clBlue) shr 16) );
end;
procedure PrepareColorTable(styleslist: TStyleRange; acolorTable: TStringList);
var
rng : TStyleRange;
i : integer;
t : string;
begin
rng:=styleslist;
acolorTable.Add('');
while Assigned(rng) do begin
if rng.font.Color=clBlack then
rng.colorId:=0
else begin
t:=ColorToRtfText(rng.font.Color);
i:=acolorTable.IndexOf(t);
if i<0 then i:=acolorTable.Add(t);
rng.colorId:=i;
end;
rng:=rng.next;
end;
// {\f0\fswiss\fcharset0 Arial;}
end;
function GetRTFWriteText(const u: UnicodeString; var idx : integer; var isNewPara: Boolean): string;
var
i : integer;
begin
Result:='';
i:=idx;
isNewPara:=false;
while i<=length(u) do begin
if u[i]='\' then Result:=Result+'\\'
else if u[i]='{' then Result:=Result+'\{'
else if u[i]='}' then Result:=Result+'\}'
else if u[i]=#10 then begin
Result:=Result+'\par ';
isNewPara:=true;
inc(i);
Break;
end else if u[i]=#13 then begin
Result:=Result+'\par ';
isNewPara:=true;
inc(i);
Break;
end else if u[i]<#127 then Result:=Result+char(byte(u[i]))
else Result:=Result+'\u'+IntToStr(word(u[i]))+' '; // adding a blank "space" character replacement
inc(i);
end;
idx:=i;
end;
procedure IntSaveStream(ARich: TCustomRichMemo; SaveParams: TSaveParams; Dst: TStream);
var
ofs : Integer;
needlen : Integer;
endless : Boolean;
root : TStyleRange; // first in the list
last : TStyleRange; // last in the list
rng : TStyleRange; // temproray
st, len : Integer;
u : UnicodeString;
fontTable : TStringList;
colorTable : TStringList;
i : Integer;
isnewpara : Boolean;
s : string;
isbold : Boolean;
isitalic : Boolean;
isuline : Boolean;
issupersub: Boolean;
isColor : integer;
pm : TParaMetric;
procedure RtfOut(const s: string);
begin
Dst.Write(s[1], length(s));
end;
begin
if SaveParams.start<0 then ofs:=0
else ofs:=SaveParams.start;
root:=nil;
last:=nil;
needlen:=SaveParams.len;
endless:=needlen<0;
while ARich.GetStyleRange(ofs, st, len) do begin
rng:=TStyleRange.Create;
rng.textStart:=st;
if not endless then begin
if needlen<len then rng.textLength:=needlen
else rng.textLength:=len;
dec(needLen, len);
end else
rng.textLength:=len;
ARich.GetTextAttributes(ofs, rng.font);
if not Assigned(root) then root:=rng;
if Assigned(last) then last.next:=rng;
last:=rng;
inc(ofs, len);
if not endless and (needLen<=0) then break;
end;
if root=nil then begin
// GetStyleRange failed - fallback to simple style export!
root:=TStyleRange.Create;
root.textStart:=0;
root.textLength:=MaxInt;
root.font.Name:=ARich.Font.Name;
root.font.Size:=ARich.Font.Size;
end;
fontTable:=TStringList.Create;
colorTable:=TStringList.Create;
try
PrepareFontTable(root, fontTable);
PrepareColorTable(root, colorTable);
RtfOut('{\rtf1\ansi\ansicp1252\deff0\deflan1033');
// start of RTF
if fontTable.Count>0 then begin
// at least on font should be present anyway.
RtfOut('{\fonttbl');
for i:=0 to fontTable.Count-1 do begin
// setting font id, charset to 0 and name
RtfOut('{\f'+IntToStR(i)+'\fcharset0 '+fontTable[i]+';}');
end;
RtfOut('}');
end;
if colorTable.Count>1 then begin
RtfOut('{\colortbl');
for i:=0 to colorTable.Count-1 do begin
RtfOut( colortable[i] );
RtfOut( ';');
end;
RtfOut('}');
end;
isnewpara := true;
rng:=root;
isbold:=false;
isitalic:=false;
issupersub:=false;
iscolor:=0;
while Assigned(rng) do begin
u:=ARich.GetUText(rng.textStart, rng.textLength);
RtfOut('\f'+IntToStr(rng.fontId));
RtfOut('\fs'+IntToStr(rng.font.Size*2));
if (fsBold in rng.font.Style) then begin
RtfOut('\b');
isbold:=true;
end else begin
if isbold then RtfOut('\b0');
isbold:=false;
end;
if (fsUnderline in rng.font.Style) then begin
RtfOut('\ul');
isuline:=true
end else begin
if isuline then RtfOut('\ulnone');
isuline:=false;
end;
if isColor<>rng.colorId then begin
RtfOut('\cf'+IntToStR(rng.colorId));
isColor:=rng.ColorId;
end;
if (fsItalic in rng.font.Style) then begin
RtfOut('\i');
isitalic:=true;
end else begin
if isitalic then RtfOut('\i0');
isitalic:=false;
end;
if rng.font.VScriptPos=vpSuperScript then begin
RtfOut('\super');
issupersub:=true;
end;
if rng.font.VScriptPos=vpSubScript then begin
RtfOut('\sub');
issupersub:=true;
end;
if rng.font.VScriptPos=vpNormal then begin
if issupersub then RtfOut('\nosupersub');
issupersub:=false;
end;
RtfOut(' ');
i:=1;
while i<=length(u) do begin
if isNewPara then begin
ARich.GetParaMetric(i+rng.textStart, pm);
RtfOut('\pard');
case ARich.GetParaAlignment(i+rng.TextStart) of
paRight: RtfOut('\qr');
paCenter: RtfOut('\qc');
paJustify: RtfOut('\qj');
else
end;
RtfOut('\li'+IntToStr(round(pm.HeadIndent*20)));
if pm.FirstLine-pm.HeadIndent<>0 then
RtfOut('\fi'+IntToStr(round((pm.FirstLine-pm.HeadIndent)*20)));
if pm.TailIndent<>0 then RtfOut('\ri'+IntToStr(round(pm.TailIndent*20)));
if pm.SpaceAfter<>0 then RtfOut('\sa'+IntToStr(round(pm.SpaceAfter*20)));
if pm.SpaceBefore<>0 then RtfOut('\sb'+IntToStr(round(pm.SpaceBefore*20)));
if pm.LineSpacing<>0 then RtfOut('\sl'+IntToStr(round(pm.LineSpacing*200))+'\slmult1');
RtfOut(' ');
end;
s:=GetRTFWriteText(u, i, isnewpara);
RtfOut(s);
end;
rng:=rng.next;
end;
// end of RTF
RtfOut('}');
finally
fontTable.Free;
colorTable.Free;
end;
FreeStyleList(root);
end;
initialization
end.