@@ -45,11 +45,34 @@ TActiveTextRTFStyleMap = class(TObject)
4545type
4646 TActiveTextRTF = class (TObject)
4747 strict private
48+ const
49+ // Difference between indent levels in twips
50+ IndentDelta = 360 ;
51+ // RTF Bullet character
52+ Bullet = #$2022 ;
53+ type
54+ TListKind = (lkNumber, lkBullet);
55+ TListState = record
56+ public
57+ ListNumber: Cardinal;
58+ ListKind: TListKind;
59+ constructor Create(AListKind: TListKind);
60+ end ;
61+ TLIState = record
62+ IsFirstPara: Boolean;
63+ Prefix: string;
64+ constructor Create(AIsFirstPara: Boolean; const APrefix: string);
65+ end ;
4866 var
4967 fElemStyleMap: TActiveTextRTFStyleMap;
5068 fDisplayURLs: Boolean;
5169 fURLStyle: TRTFStyle;
52- fInBlock: Boolean;
70+ fBlockStack: TStack<TActiveTextActionElemKind>;
71+ fListStack: TStack<TListState>;
72+ fIndentStack: TStack<SmallInt>;
73+ fLIStack: TStack<TLIState>;
74+ fIndentLevel: Byte; // logical indent level
75+ fInPara: Boolean;
5376 procedure SetElemStyleMap (const ElemStyleMap: TActiveTextRTFStyleMap);
5477 procedure Initialise (const Builder: TRTFBuilder);
5578 procedure RenderTextElem (Elem: IActiveTextTextElem;
@@ -60,6 +83,7 @@ TActiveTextRTF = class(TObject)
6083 const Builder: TRTFBuilder);
6184 procedure RenderURL (Elem: IActiveTextActionElem;
6285 const Builder: TRTFBuilder);
86+ function CanEmitInline : Boolean;
6387 public
6488 constructor Create;
6589 destructor Destroy; override;
@@ -76,8 +100,10 @@ implementation
76100
77101
78102uses
103+ // Delphi
104+ SysUtils, Generics.Defaults,
79105 // Project
80- SysUtils, Generics.Defaults ;
106+ UConsts, UStrUtils ;
81107
82108
83109{ TActiveTextRTFStyleMap }
@@ -155,15 +181,32 @@ procedure TActiveTextRTFStyleMap.MakeMonochrome;
155181
156182{ TActiveTextRTF }
157183
184+ function TActiveTextRTF.CanEmitInline : Boolean;
185+ begin
186+ if fBlockStack.Count <= 0 then
187+ Exit(False);
188+ Result := TActiveTextElemCaps.CanContainText(fBlockStack.Peek);
189+ end ;
190+
158191constructor TActiveTextRTF.Create;
159192begin
160193 inherited Create;
161194 fElemStyleMap := TActiveTextRTFStyleMap.Create;
162195 fURLStyle := TRTFStyle.CreateNull;
196+ fBlockStack := TStack<TActiveTextActionElemKind>.Create;
197+ fListStack := TStack<TListState>.Create;
198+ fIndentStack := TStack<SmallInt>.Create;
199+ fLIStack := TStack<TLIState>.Create;
200+ fIndentLevel := 0 ;
201+ fInPara := False;
163202end ;
164203
165204destructor TActiveTextRTF.Destroy;
166205begin
206+ fLIStack.Free;
207+ fIndentStack.Free;
208+ fListStack.Free;
209+ fBlockStack.Free;
167210 fElemStyleMap.Free;
168211 inherited ;
169212end ;
@@ -189,7 +232,6 @@ procedure TActiveTextRTF.Render(ActiveText: IActiveText;
189232 ActionElem: IActiveTextActionElem;
190233begin
191234 Initialise(RTFBuilder);
192- fInBlock := False;
193235 for Elem in ActiveText do
194236 begin
195237 if Supports(Elem, IActiveTextTextElem, TextElem) then
@@ -206,27 +248,154 @@ procedure TActiveTextRTF.Render(ActiveText: IActiveText;
206248
207249procedure TActiveTextRTF.RenderBlockActionElem (Elem: IActiveTextActionElem;
208250 const Builder: TRTFBuilder);
251+
252+ procedure OpenListContainer (const ListKind: TListKind);
253+ begin
254+ fListStack.Push(TListState.Create(ListKind));
255+ Inc(fIndentLevel);
256+ Builder.BeginGroup;
257+ end ;
258+
259+ function IndentTwips : SmallInt;
260+ begin
261+ Result := fElemStyleMap[ekListItem].IndentLevelToTwips(fIndentLevel)
262+ end ;
263+
264+ var
265+ ListState: TListState;
266+ LIState: TLIState;
267+ Style: TRTFStyle;
209268begin
210269 case Elem.State of
211270 fsOpen:
212271 begin
213- fInBlock := True;
214- Builder.BeginGroup;
215- Builder.ApplyStyle(fElemStyleMap[Elem.Kind]);
272+ fInPara := False;
273+ fBlockStack.Push(Elem.Kind);
274+ case Elem.Kind of
275+ ekPara, ekHeading, ekBlock:
276+ begin
277+ Builder.BeginGroup;
278+ Style := fElemStyleMap[Elem.Kind];
279+ if fLIStack.Count > 0 then
280+ begin
281+ Builder.SetTabStops([IndentTwips]);
282+ if fLIStack.Peek.IsFirstPara then
283+ begin
284+ Builder.SetIndents(
285+ IndentTwips, -fElemStyleMap[ekListItem].IndentDelta
286+ );
287+ if (fListStack.Count > 0 ) then
288+ begin
289+ if fListStack.Peek.ListNumber = 1 then
290+ begin
291+ Style.Capabilities := Style.Capabilities + [scParaSpacing];
292+ if fListStack.Peek.ListKind = lkNumber then
293+ Style.ParaSpacing := TRTFParaSpacing.Create(
294+ fElemStyleMap[ekOrderedList].ParaSpacing.Before, 0.0
295+ )
296+ else
297+ Style.ParaSpacing := TRTFParaSpacing.Create(
298+ fElemStyleMap[ekUnorderedList].ParaSpacing.Before, 0.0
299+ )
300+ end
301+ else if fListStack.Peek.ListNumber > 1 then
302+ begin
303+ if Elem.Kind = ekHeading then
304+ begin
305+ Style.Capabilities := Style.Capabilities + [scParaSpacing];
306+ Style.ParaSpacing := fElemStyleMap[ekPara].ParaSpacing;
307+ end ;
308+ end ;
309+ end ;
310+ Builder.ApplyStyle(Style);
311+ Builder.AddText(fLIStack.Peek.Prefix);
312+ Builder.AddText(TAB);
313+ fInPara := True;
314+ end
315+ else
316+ begin
317+ Builder.ApplyStyle(Style);
318+ Builder.SetIndents(IndentTwips, 0 );
319+ end ;
320+ end
321+ else
322+ begin
323+ Builder.ApplyStyle(Style);
324+ Builder.SetIndents(IndentTwips, 0 );
325+ end ;
326+ end ;
327+ ekUnorderedList:
328+ OpenListContainer(lkBullet);
329+ ekOrderedList:
330+ OpenListContainer(lkNumber);
331+ ekListItem:
332+ begin
333+ // Update list number of current list
334+ ListState := fListStack.Pop;
335+ Inc(ListState.ListNumber, 1 );
336+ fListStack.Push(ListState);
337+ Builder.BeginGroup;
338+ Builder.ApplyStyle(fElemStyleMap[Elem.Kind]);
339+ case fListStack.Peek.ListKind of
340+ lkNumber:
341+ begin
342+ fLIStack.Push(
343+ TLIState.Create(
344+ True, IntToStr(fListStack.Peek.ListNumber) + ' .'
345+ )
346+ );
347+ end ;
348+ lkBullet:
349+ begin
350+ fLIStack.Push(TLIState.Create(True, Bullet));
351+ end ;
352+ end ;
353+ Builder.ClearParaFormatting;
354+ end ;
355+ end ;
216356 end ;
217357 fsClose:
218358 begin
219- Builder.EndPara;
220- Builder.EndGroup;
221- fInBlock := False;
359+ case Elem.Kind of
360+ ekPara, ekHeading, ekBlock:
361+ begin
362+ if (fLIStack.Count > 0 ) and (fLIStack.Peek.IsFirstPara) then
363+ begin
364+ // Update item at top of LI stack to record not first para
365+ LIState := fLIStack.Pop;
366+ LIState.IsFirstPara := False;
367+ fLIStack.Push(LIState);
368+ end ;
369+ if fInPara then
370+ Builder.EndPara;
371+ Builder.EndGroup;
372+ end ;
373+ ekUnorderedList, ekOrderedList:
374+ begin
375+ if fInPara then
376+ Builder.EndPara;
377+ Builder.EndGroup;
378+ fListStack.Pop;
379+ Dec(fIndentLevel);
380+ end ;
381+ ekListItem:
382+ begin
383+ if fInPara then
384+ Builder.EndPara;
385+ Builder.EndGroup;
386+ fLIStack.Pop;
387+ end ;
388+ end ;
389+ fBlockStack.Pop;
390+ fInPara := False;
222391 end ;
223392 end ;
224393end ;
225394
226395procedure TActiveTextRTF.RenderInlineActionElem (Elem: IActiveTextActionElem;
227396 const Builder: TRTFBuilder);
228397begin
229- if not fInBlock then
398+ if not CanEmitInline then
230399 Exit;
231400 case Elem.State of
232401 fsOpen:
@@ -245,10 +414,20 @@ procedure TActiveTextRTF.RenderInlineActionElem(Elem: IActiveTextActionElem;
245414
246415procedure TActiveTextRTF.RenderTextElem (Elem: IActiveTextTextElem;
247416 const Builder: TRTFBuilder);
417+ var
418+ TheText: string;
248419begin
249- if not fInBlock then
420+ if not CanEmitInline then
250421 Exit;
251- Builder.AddText(Elem.Text);
422+ TheText := Elem.Text;
423+ // no white space emitted after block start until 1st non-white space
424+ // character encountered
425+ if not fInPara then
426+ TheText := StrTrimLeft(Elem.Text);
427+ if TheText = ' ' then
428+ Exit;
429+ Builder.AddText(TheText);
430+ fInPara := True;
252431end ;
253432
254433procedure TActiveTextRTF.RenderURL (Elem: IActiveTextActionElem;
@@ -271,5 +450,22 @@ procedure TActiveTextRTF.SetElemStyleMap(
271450 fElemStyleMap.Assign(ElemStyleMap);
272451end ;
273452
453+ { TActiveTextRTF.TListState }
454+
455+ constructor TActiveTextRTF.TListState.Create(AListKind: TListKind);
456+ begin
457+ ListNumber := 0 ;
458+ ListKind := AListKind;
459+ end ;
460+
461+ { TActiveTextRTF.TLIState }
462+
463+ constructor TActiveTextRTF.TLIState.Create(AIsFirstPara: Boolean;
464+ const APrefix: string);
465+ begin
466+ IsFirstPara := AIsFirstPara;
467+ Prefix := APrefix;
468+ end ;
469+
274470end .
275471
0 commit comments