
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@7314 8e941d3f-bd1b-0410-a28a-d453659cc2b4
980 lines
27 KiB
ObjectPascal
980 lines
27 KiB
ObjectPascal
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.
|