| 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. | 
|---|