1 | Unit RichTextDisplayUnit;
|
---|
2 |
|
---|
3 | Interface
|
---|
4 |
|
---|
5 | uses
|
---|
6 | Classes,
|
---|
7 | CanvasFontManager,
|
---|
8 | RichTextStyleUnit, RichTextLayoutUnit;
|
---|
9 |
|
---|
10 | // Selection start and end should both be nil if no selection is to be applied
|
---|
11 | Procedure DrawRichTextLayout( const FontManager: TCanvasFontManager;
|
---|
12 | const Layout: TRichTextLayout;
|
---|
13 | const SelectionStart: PChar;
|
---|
14 | const SelectionEnd: PChar;
|
---|
15 | const StartLine: longint;
|
---|
16 | const EndLine: longint;
|
---|
17 | const StartPoint: TPoint );
|
---|
18 |
|
---|
19 | // Print as much of the given layout as will fit on the page,
|
---|
20 | // starting at StartY and StartLine
|
---|
21 | // EndY is set to the final Y output position used + 1.
|
---|
22 | // EndLine is set to the last line printed + 1
|
---|
23 | Procedure PrintRichTextLayout( const FontManager: TCanvasFontManager;
|
---|
24 | const Layout: TRichTextLayout;
|
---|
25 | const StartLine: longint;
|
---|
26 | var EndLine: longint;
|
---|
27 | const StartY: longint;
|
---|
28 | var EndY: longint );
|
---|
29 |
|
---|
30 | Implementation
|
---|
31 |
|
---|
32 | uses
|
---|
33 | SysUtils,
|
---|
34 | Forms, Graphics,
|
---|
35 | ACLString, ACLUtility,
|
---|
36 | RichTextDocumentUnit;
|
---|
37 |
|
---|
38 | // For the given point in the text, update selected if the point
|
---|
39 | // is at start or end of selection
|
---|
40 | // Returns true if changed
|
---|
41 | function SelectionChange( P: PChar;
|
---|
42 | SelectionStart: PChar;
|
---|
43 | SelectionEnd: PChar;
|
---|
44 | var NextSelected: boolean ): boolean;
|
---|
45 | begin
|
---|
46 | Result := false;
|
---|
47 | if P = SelectionStart then
|
---|
48 | begin
|
---|
49 | Result := true;
|
---|
50 | if SelectionStart < SelectionEnd then
|
---|
51 | // reached start of selection
|
---|
52 | NextSelected := true
|
---|
53 | else
|
---|
54 | // reached end
|
---|
55 | NextSelected := false;
|
---|
56 | end
|
---|
57 | else if P = SelectionEnd then
|
---|
58 | begin
|
---|
59 | Result := true;
|
---|
60 | if SelectionStart < SelectionEnd then
|
---|
61 | // reached end of selection
|
---|
62 | NextSelected := false
|
---|
63 | else
|
---|
64 | // reached start
|
---|
65 | NextSelected := true;
|
---|
66 | end;
|
---|
67 | end;
|
---|
68 |
|
---|
69 | function InvertRGB( Arg: TColor ): TColor;
|
---|
70 | begin
|
---|
71 | Result := SysColorToRGB( Arg ); // in case it's a system color e.g. button face
|
---|
72 | Result := Result xor $ffffff; // now invert the RGB components
|
---|
73 | end;
|
---|
74 |
|
---|
75 | // Draw a string at the given location with given color/selected state
|
---|
76 | Procedure DrawRichTextString( FontManager: TCanvasFontManager;
|
---|
77 | Var X: longint;
|
---|
78 | Y: longint;
|
---|
79 | S: PChar;
|
---|
80 | Len: longint;
|
---|
81 | Selected: Boolean;
|
---|
82 | PenColor: TColor;
|
---|
83 | BackColor: TColor );
|
---|
84 | var
|
---|
85 | Point: TPoint;
|
---|
86 | begin
|
---|
87 | if Len = 0 then
|
---|
88 | exit;
|
---|
89 |
|
---|
90 | Point.X := X;
|
---|
91 | Point.Y := Y;
|
---|
92 | { if FDebug then
|
---|
93 | begin
|
---|
94 | // generate a random dark color
|
---|
95 | Canvas.Brush.Color := random( 191 ) * 65536 //r
|
---|
96 | + random( 191 ) * 256 //g
|
---|
97 | + random( 191 ); //b
|
---|
98 | Canvas.Pen.Color := clWhite;
|
---|
99 | end
|
---|
100 | else}
|
---|
101 |
|
---|
102 | if Selected then
|
---|
103 | begin
|
---|
104 | FontManager.Canvas.Brush.Color := InvertRGB( BackColor );
|
---|
105 | FontManager.Canvas.Pen.Color := InvertRGB( PenColor );
|
---|
106 | end
|
---|
107 | else
|
---|
108 | begin
|
---|
109 | FontManager.Canvas.Brush.Color := BackColor;
|
---|
110 | FontManager.Canvas.Pen.Color := PenColor;
|
---|
111 | end;
|
---|
112 | FontManager.DrawString( Point, Len, S );
|
---|
113 | X := Point.X;
|
---|
114 | end;
|
---|
115 |
|
---|
116 | var
|
---|
117 | // global, so that we don't reallocate every drawline
|
---|
118 | StringToDraw: TAString = nil;
|
---|
119 |
|
---|
120 | // Draw the specified line at the specified
|
---|
121 | // (physical) location
|
---|
122 | Procedure DrawRichTextLine( FontManager: TCanvasFontManager;
|
---|
123 | Layout: TRichTextLayout;
|
---|
124 | SelectionStart: PChar;
|
---|
125 | SelectionEnd: PChar;
|
---|
126 |
|
---|
127 | Line: TLayoutLine;
|
---|
128 | Start: TPoint );
|
---|
129 | var
|
---|
130 | X, Y: longint;
|
---|
131 | Element: TTextElement;
|
---|
132 | StartedDrawing: boolean;
|
---|
133 | Style: TTextDrawStyle;
|
---|
134 | P: PChar;
|
---|
135 | NextP: PChar;
|
---|
136 | EndP: PChar;
|
---|
137 |
|
---|
138 | BitmapIndex: longint;
|
---|
139 | Bitmap: TBitmap;
|
---|
140 |
|
---|
141 | BitmapRect: TRect;
|
---|
142 |
|
---|
143 | TextBlockStart: PChar;
|
---|
144 |
|
---|
145 | Selected: boolean;
|
---|
146 | NextSelected: boolean;
|
---|
147 |
|
---|
148 | NewMarginX: longint;
|
---|
149 |
|
---|
150 | procedure DrawTextBlock;
|
---|
151 | var
|
---|
152 | PhysX: longint;
|
---|
153 | begin
|
---|
154 | PhysX := X div FontWidthPrecisionFactor;
|
---|
155 |
|
---|
156 | DrawRichTextString( FontManager,
|
---|
157 | PhysX,
|
---|
158 | Y,
|
---|
159 | StringToDraw.AsPChar,
|
---|
160 | StringToDraw.Length,
|
---|
161 | Selected,
|
---|
162 | Style.Color,
|
---|
163 | Style.BackgroundColor );
|
---|
164 | X := PhysX * FontWidthPrecisionFactor;
|
---|
165 | StringToDraw.AssignString( '' );
|
---|
166 | end;
|
---|
167 |
|
---|
168 |
|
---|
169 | begin
|
---|
170 | P := Line.Text;
|
---|
171 | EndP := Line.Text + Line.Length;
|
---|
172 |
|
---|
173 | if P = EndP then
|
---|
174 | begin
|
---|
175 | // Empty line
|
---|
176 | exit;
|
---|
177 | end;
|
---|
178 |
|
---|
179 | Selected := false;
|
---|
180 | if SelectionStart <= Line.Text then
|
---|
181 | // selection start is above.
|
---|
182 | Selected := true;
|
---|
183 | if SelectionEnd <= Line.Text then
|
---|
184 | // selection end is above.
|
---|
185 | Selected := not Selected;
|
---|
186 |
|
---|
187 | if StringToDraw = nil then
|
---|
188 | StringToDraw := TAString.Create;
|
---|
189 |
|
---|
190 | Style := Line.Style;
|
---|
191 | FontManager.SetFont( Style.Font );
|
---|
192 | StartedDrawing := false;
|
---|
193 |
|
---|
194 | TextBlockStart := P;
|
---|
195 |
|
---|
196 | Y := Start.Y + Line.MaxDescender;
|
---|
197 |
|
---|
198 | while P < EndP do
|
---|
199 | begin
|
---|
200 | Element := ExtractNextTextElement( P, NextP );
|
---|
201 |
|
---|
202 | if SelectionChange( P,
|
---|
203 | SelectionStart,
|
---|
204 | SelectionEnd,
|
---|
205 | NextSelected ) then
|
---|
206 | begin
|
---|
207 | DrawTextBlock;
|
---|
208 | TextBlockStart := P;
|
---|
209 | Selected := NextSelected;
|
---|
210 | end;
|
---|
211 |
|
---|
212 | case Element.ElementType of
|
---|
213 | teWordBreak,
|
---|
214 | teText,
|
---|
215 | teImage:
|
---|
216 | begin
|
---|
217 | if not StartedDrawing then
|
---|
218 | begin
|
---|
219 | // we haven't yet started drawing:
|
---|
220 | // so work out alignment
|
---|
221 | X := Start.X * FontWidthPrecisionFactor
|
---|
222 | + Layout.GetStartX( Style, Line );
|
---|
223 | StartedDrawing := true;
|
---|
224 | end;
|
---|
225 |
|
---|
226 | // Now do the drawing
|
---|
227 | if Element.ElementType = teImage then
|
---|
228 | begin
|
---|
229 | DrawTextBlock;
|
---|
230 | TextBlockStart := NextP;
|
---|
231 |
|
---|
232 | try
|
---|
233 | BitmapIndex := StrToInt( Element.Tag.Arguments );
|
---|
234 | except
|
---|
235 | BitmapIndex := -1;
|
---|
236 | end;
|
---|
237 | if Layout.IsValidBitmapIndex( BitmapIndex ) then
|
---|
238 | begin
|
---|
239 | Bitmap := Layout.Images.GetBitmapReference( BitmapIndex );
|
---|
240 |
|
---|
241 | BitmapRect.Left := X div FontWidthPrecisionFactor;
|
---|
242 | BitmapRect.Bottom := Start.Y;
|
---|
243 | BitmapRect.Right := BitmapRect.Left
|
---|
244 | + Bitmap.Width
|
---|
245 | * Layout.HorizontalImageScale;
|
---|
246 | BitmapRect.Top := BitmapRect.Bottom
|
---|
247 | + Bitmap.Height
|
---|
248 | * Layout.VerticalImageScale;;
|
---|
249 |
|
---|
250 | Bitmap.Draw( FontManager.Canvas,
|
---|
251 | BitmapRect );
|
---|
252 |
|
---|
253 |
|
---|
254 | inc( X,
|
---|
255 | trunc( Bitmap.Width
|
---|
256 | * FontWidthPrecisionFactor
|
---|
257 | * Layout.HorizontalImageScale ) );
|
---|
258 | end;
|
---|
259 | end
|
---|
260 | else
|
---|
261 | begin
|
---|
262 | // character (or word break)
|
---|
263 | // build up the successive characters...
|
---|
264 | StringToDraw.AddString( Element.Character );
|
---|
265 | end;
|
---|
266 | end;
|
---|
267 |
|
---|
268 | teStyle:
|
---|
269 | begin
|
---|
270 | DrawTextBlock;
|
---|
271 | TextBlockStart := NextP;
|
---|
272 |
|
---|
273 | if ( Element.Tag.TagType = ttItalicOff )
|
---|
274 | and ( faItalic in Style.Font.Attributes )
|
---|
275 | and ( not FontManager.IsFixed )
|
---|
276 | then
|
---|
277 | // end of italic; add a space
|
---|
278 | inc( X, FontManager.CharWidth( ' ' ) );
|
---|
279 |
|
---|
280 | Layout.PerformStyleTag( Element.Tag,
|
---|
281 | Style,
|
---|
282 | X );
|
---|
283 | NewMarginX := ( Start.X + Style.LeftMargin ) * FontWidthPrecisionFactor;
|
---|
284 | if NewMarginX > X then
|
---|
285 | begin
|
---|
286 | //skip across...
|
---|
287 | X := NewMarginX;
|
---|
288 | end;
|
---|
289 | end;
|
---|
290 | end;
|
---|
291 | P := NextP;
|
---|
292 | end;
|
---|
293 |
|
---|
294 | DrawTextBlock;
|
---|
295 | end;
|
---|
296 |
|
---|
297 | Procedure DrawRichTextLayout( const FontManager: TCanvasFontManager;
|
---|
298 | const Layout: TRichTextLayout;
|
---|
299 | const SelectionStart: PChar;
|
---|
300 | const SelectionEnd: PChar;
|
---|
301 | const StartLine: longint;
|
---|
302 | const EndLine: longint;
|
---|
303 | const StartPoint: TPoint );
|
---|
304 | Var
|
---|
305 | Line: TLayoutLine;
|
---|
306 | LineIndex: longint;
|
---|
307 |
|
---|
308 | Y: longint;
|
---|
309 |
|
---|
310 | BottomOfLine: longint;
|
---|
311 | begin
|
---|
312 | assert( StartLine >= 0 );
|
---|
313 | assert( StartLine <= Layout.FNumLines );
|
---|
314 | assert( EndLine >= 0 );
|
---|
315 | assert( EndLine <= Layout.FNumLines );
|
---|
316 | assert( StartLine <= EndLine );
|
---|
317 |
|
---|
318 | if Layout.FNumLines = 0 then
|
---|
319 | // no text to draw
|
---|
320 | exit;
|
---|
321 |
|
---|
322 | Y := StartPoint.Y
|
---|
323 | - Layout.FRichTextSettings.Margins.Top;
|
---|
324 |
|
---|
325 | LineIndex := 0;
|
---|
326 |
|
---|
327 | repeat
|
---|
328 | Line := Layout.FLines[ LineIndex ];
|
---|
329 | BottomOfLine := Y - Line.Height + 1; // bottom pixel row is top - height + 1
|
---|
330 |
|
---|
331 | if // the line is in the range to be drawn
|
---|
332 | ( LineIndex >= StartLine )
|
---|
333 | and ( LineIndex <= EndLine )
|
---|
334 |
|
---|
335 | // and the line is within the cliprect
|
---|
336 | and ( BottomOfLine <= FontManager.Canvas.ClipRect.Top )
|
---|
337 | and ( Y > FontManager.Canvas.ClipRect.Bottom ) then
|
---|
338 | begin
|
---|
339 | // draw it. First decided whether selection is started or not.
|
---|
340 | DrawRichTextLine( FontManager,
|
---|
341 | Layout,
|
---|
342 | SelectionStart,
|
---|
343 | SelectionEnd,
|
---|
344 | Line,
|
---|
345 | Point( StartPoint.X,
|
---|
346 | BottomOfLine ) );
|
---|
347 |
|
---|
348 | end;
|
---|
349 | dec( Y, Line.Height );
|
---|
350 |
|
---|
351 | if Y < 0 then
|
---|
352 | // past bottom of output canvas
|
---|
353 | break;
|
---|
354 |
|
---|
355 | inc( LineIndex );
|
---|
356 |
|
---|
357 | if LineIndex >= Layout.FNumLines then
|
---|
358 | // end of text
|
---|
359 | break;
|
---|
360 |
|
---|
361 | until false;
|
---|
362 |
|
---|
363 | End;
|
---|
364 |
|
---|
365 | Procedure PrintRichTextLayout( const FontManager: TCanvasFontManager;
|
---|
366 | const Layout: TRichTextLayout;
|
---|
367 | const StartLine: longint;
|
---|
368 | var EndLine: longint;
|
---|
369 | const StartY: longint;
|
---|
370 | var EndY: longint );
|
---|
371 | Var
|
---|
372 | Selected: boolean;
|
---|
373 | Line: TLayoutLine;
|
---|
374 | LineIndex: longint;
|
---|
375 |
|
---|
376 | Y: longint;
|
---|
377 |
|
---|
378 | BottomOfLine: longint;
|
---|
379 |
|
---|
380 | LinesPrinted: longint;
|
---|
381 | begin
|
---|
382 | assert( StartLine >= 0 );
|
---|
383 | assert( StartLine <= Layout.FNumLines );
|
---|
384 |
|
---|
385 | if Layout.FNumLines = 0 then
|
---|
386 | // no text to draw
|
---|
387 | exit;
|
---|
388 |
|
---|
389 | Y := StartY;
|
---|
390 | // - Layout.FRichTextSettings.Margins.Top;
|
---|
391 |
|
---|
392 | Selected := false; // it's not going to change.
|
---|
393 |
|
---|
394 | LinesPrinted := 0;
|
---|
395 |
|
---|
396 | LineIndex := StartLine;
|
---|
397 |
|
---|
398 | repeat
|
---|
399 | Line := Layout.FLines[ LineIndex ];
|
---|
400 | BottomOfLine := Y - Line.Height + 1; // bottom pixel row is top - height + 1
|
---|
401 |
|
---|
402 | if BottomOfLine < Layout.FRichTextSettings.Margins.Bottom then
|
---|
403 | // past bottom of page (less margin)
|
---|
404 | if LinesPrinted > 0 then
|
---|
405 | // stop, as long as we've printed at least 1 line
|
---|
406 | break;
|
---|
407 |
|
---|
408 | // draw it
|
---|
409 | DrawRichTextLine( FontManager,
|
---|
410 | Layout,
|
---|
411 | nil,
|
---|
412 | nil,
|
---|
413 | Line,
|
---|
414 | Point( 0,
|
---|
415 | BottomOfLine ) );
|
---|
416 |
|
---|
417 | dec( Y, Line.Height );
|
---|
418 |
|
---|
419 | inc( LinesPrinted );
|
---|
420 |
|
---|
421 | inc( LineIndex );
|
---|
422 |
|
---|
423 | if LineIndex >= Layout.FNumLines then
|
---|
424 | // end of text
|
---|
425 | break;
|
---|
426 |
|
---|
427 | until false;
|
---|
428 |
|
---|
429 | EndY := Y;
|
---|
430 | EndLine := LineIndex;
|
---|
431 | End;
|
---|
432 |
|
---|
433 | Initialization
|
---|
434 | End.
|
---|