source: trunk/Packages/bgrabitmap/bgratypewriter.pas

Last change on this file was 2, checked in by chronos, 5 years ago
File size: 26.6 KB
Line 
1unit BGRATypewriter;
2
3{$mode objfpc}{$H+}
4
5interface
6
7uses
8 Classes, SysUtils, AvgLvlTree, BGRABitmapTypes, BGRACanvas2D, BGRATransform;
9
10type
11 TGlyphBoxes = array of record
12 Glyph: string;
13 Box: TAffineBox;
14 end;
15
16 { TBGRAGlyph }
17
18 TBGRAGlyph = class
19 protected
20 FIdentifier: string;
21 procedure WriteHeader(AStream: TStream; AName: string; AContentSize: longint);
22 class procedure ReadHeader(AStream: TStream; out AName: string; out AContentSize: longint);
23 function ContentSize: integer; virtual;
24 function HeaderName: string; virtual;
25 procedure WriteContent(AStream: TStream); virtual;
26 procedure ReadContent(AStream: TStream); virtual;
27 public
28 Width,Height: single;
29 constructor Create(AIdentifier: string); virtual;
30 constructor Create(AStream: TStream); virtual;
31 procedure Path({%H-}ADest: IBGRAPath; {%H-}AMatrix: TAffineMatrix); virtual;
32 property Identifier: string read FIdentifier;
33 procedure SaveToStream(AStream: TStream);
34 class function LoadFromStream(AStream: TStream): TBGRAGlyph;
35 end;
36
37 TGlyphPointCurveMode= TEasyBezierCurveMode;
38
39const
40 cmAuto = TEasyBezierCurveMode.cmAuto;
41 cmCurve = TEasyBezierCurveMode.cmCurve;
42 cmAngle = TEasyBezierCurveMode.cmAngle;
43
44type
45 { TBGRAPolygonalGlyph }
46
47 TBGRAPolygonalGlyph = class(TBGRAGlyph)
48 private
49 function GetClosed: boolean;
50 function GetMinimumDotProduct: single;
51 function GetPoint(AIndex: integer): TPointF;
52 function GetPointCount: integer;
53 procedure SetClosed(AValue: boolean);
54 procedure SetMinimumDotProduct(AValue: single);
55 procedure SetPoint(AIndex: integer; AValue: TPointF);
56 procedure SetQuadraticCurves(AValue: boolean);
57 protected
58 FQuadraticCurves: boolean;
59 FEasyBezier: TEasyBezierCurve;
60 function ContentSize: integer; override;
61 function HeaderName: string; override;
62 procedure WriteContent(AStream: TStream); override;
63 procedure ReadContent(AStream: TStream); override;
64 function PointTransformMatrix(APoint: PPointF; AData: pointer): TPointF;
65 procedure Init;
66 public
67 Offset: TPointF;
68 constructor Create(AIdentifier: string); override;
69 constructor Create(AStream: TStream); override;
70 constructor Create(AStream: TStream; AQuadratic: boolean);
71 procedure SetPoints(const APoints: array of TPointF); overload;
72 procedure SetPoints(const APoints: array of TPointF; const ACurveMode: array of TGlyphPointCurveMode); overload;
73 procedure Path(ADest: IBGRAPath; AMatrix: TAffineMatrix); override;
74 property QuadraticCurves: boolean read FQuadraticCurves write SetQuadraticCurves;
75 property Closed: boolean read GetClosed write SetClosed;
76 property MinimumDotProduct: single read GetMinimumDotProduct write SetMinimumDotProduct;
77 property Point[AIndex: integer]: TPointF read GetPoint write SetPoint;
78 property PointCount: integer read GetPointCount;
79 end;
80
81 TBGRACustomTypeWriterHeader = record
82 HeaderName: String;
83 NbGlyphs: integer;
84 end;
85
86 { TBGRACustomTypeWriter }
87
88 TBGRACustomTypeWriter = class
89 private
90 FGlyphs: TAvgLvlTree;
91 protected
92 TypeWriterMatrix: TAffineMatrix;
93 function CompareGlyph({%H-}Tree: TAvgLvlTree; Data1, Data2: Pointer): integer;
94 function FindGlyph(AIdentifier: string): TAvgLvlTreeNode;
95 function GetGlyph(AIdentifier: string): TBGRAGlyph; virtual;
96 procedure SetGlyph(AIdentifier: string; AValue: TBGRAGlyph);
97 procedure TextPath(ADest: TBGRACanvas2D; ATextUTF8: string; X,Y: Single; AAlign: TBGRATypeWriterAlignment; ADrawEachChar: boolean);
98 procedure GlyphPath(ADest: TBGRACanvas2D; AIdentifier: string; X,Y: Single; AAlign: TBGRATypeWriterAlignment = twaTopLeft);
99 procedure DrawLastPath(ADest: TBGRACanvas2D);
100 procedure ClearGlyphs;
101 procedure RemoveGlyph(AIdentifier: string);
102 procedure AddGlyph(AGlyph: TBGRAGlyph);
103 function GetGlyphMatrix(AGlyph: TBGRAGlyph; X,Y: Single; AAlign: TBGRATypeWriterAlignment): TAffineMatrix;
104 function GetTextMatrix(ATextUTF8: string; X,Y: Single; AAlign: TBGRATypeWriterAlignment): TAffineMatrix;
105 property Glyph[AIdentifier: string]: TBGRAGlyph read GetGlyph write SetGlyph;
106 function CustomHeaderSize: integer; virtual;
107 procedure WriteCustomHeader(AStream: TStream); virtual;
108 function ReadCustomTypeWriterHeader(AStream: TStream): TBGRACustomTypeWriterHeader;
109 procedure ReadAdditionalHeader({%H-}AStream: TStream); virtual;
110 function HeaderName: string; virtual;
111 public
112 OutlineMode: TBGRATypeWriterOutlineMode;
113 DrawGlyphsSimultaneously : boolean;
114 constructor Create;
115 procedure SaveGlyphsToFile(AFilenameUTF8: string);
116 procedure SaveGlyphsToStream(AStream: TStream);
117 procedure LoadGlyphsFromFile(AFilenameUTF8: string);
118 procedure LoadGlyphsFromStream(AStream: TStream);
119 procedure DrawGlyph(ADest: TBGRACanvas2D; AIdentifier: string; X,Y: Single; AAlign: TBGRATypeWriterAlignment = twaTopLeft);
120 procedure DrawText(ADest: TBGRACanvas2D; ATextUTF8: string; X,Y: Single; AAlign: TBGRATypeWriterAlignment = twaTopLeft); virtual;
121 procedure CopyTextPathTo(ADest: IBGRAPath; ATextUTF8: string; X,Y: Single; AAlign: TBGRATypeWriterAlignment = twaTopLeft); virtual;
122 function GetGlyphBox(AIdentifier: string; X,Y: Single; AAlign: TBGRATypeWriterAlignment = twaTopLeft): TAffineBox;
123 function GetTextBox(ATextUTF8: string; X,Y: Single; AAlign: TBGRATypeWriterAlignment = twaTopLeft): TAffineBox;
124 function GetTextGlyphBoxes(ATextUTF8: string; X,Y: Single; AAlign: TBGRATypeWriterAlignment = twaTopLeft): TGlyphBoxes;
125 procedure NeedGlyphRange(AUnicodeFrom, AUnicodeTo: Cardinal);
126 procedure NeedGlyphAnsiRange;
127 destructor Destroy; override;
128 end;
129
130function ComputeEasyBezier(APoints: array of TPointF; AClosed: boolean; AMinimumDotProduct: single = 0.707): ArrayOfTPointF; overload;
131function ComputeEasyBezier(APoints: array of TPointF; ACurveMode: array of TGlyphPointCurveMode; AClosed: boolean; AMinimumDotProduct: single = 0.707): ArrayOfTPointF; overload;
132
133implementation
134
135uses BGRAUTF8;
136
137procedure LEWritePointF(Stream: TStream; AValue: TPointF);
138begin
139 LEWriteSingle(Stream,AValue.x);
140 LEWriteSingle(Stream,AValue.y);
141end;
142
143function LEReadPointF(Stream: TStream): TPointF;
144begin
145 result.x := LEReadSingle(Stream);
146 result.y := LEReadSingle(Stream);
147end;
148
149function ComputeEasyBezier(APoints: array of TPointF; AClosed: boolean; AMinimumDotProduct: single = 0.707): ArrayOfTPointF;
150var
151 glyph: TBGRAPolygonalGlyph;
152 canvas2D: TBGRACanvas2D;
153 i: integer;
154begin
155 if length(APoints) <= 2 then
156 begin
157 setlength(result, length(APoints));
158 for i := 0 to high(result) do
159 result[i] := APoints[i];
160 exit;
161 end;
162 glyph := TBGRAPolygonalGlyph.Create('');
163 glyph.QuadraticCurves := true;
164 glyph.Closed:= AClosed;
165 glyph.MinimumDotProduct := AMinimumDotProduct;
166 glyph.SetPoints(APoints);
167 canvas2D := TBGRACanvas2D.Create(nil);
168 canvas2D.pixelCenteredCoordinates := true;
169 glyph.Path(canvas2D,AffineMatrixIdentity);
170 glyph.Free;
171 result := canvas2D.currentPath;
172 canvas2D.free;
173end;
174
175function ComputeEasyBezier(APoints: array of TPointF;
176 ACurveMode: array of TGlyphPointCurveMode; AClosed: boolean;
177 AMinimumDotProduct: single): ArrayOfTPointF;
178var
179 glyph: TBGRAPolygonalGlyph;
180 canvas2D: TBGRACanvas2D;
181 i: integer;
182begin
183 if length(APoints) <= 2 then
184 begin
185 setlength(result, length(APoints));
186 for i := 0 to high(result) do
187 result[i] := APoints[i];
188 exit;
189 end;
190 glyph := TBGRAPolygonalGlyph.Create('');
191 glyph.QuadraticCurves := true;
192 glyph.Closed:= AClosed;
193 glyph.MinimumDotProduct := AMinimumDotProduct;
194 glyph.SetPoints(APoints, ACurveMode);
195 canvas2D := TBGRACanvas2D.Create(nil);
196 canvas2D.pixelCenteredCoordinates := true;
197 glyph.Path(canvas2D,AffineMatrixIdentity);
198 glyph.Free;
199 result := canvas2D.currentPath;
200 canvas2D.free;
201end;
202
203{ TBGRAPolygonalGlyph }
204
205function TBGRAPolygonalGlyph.GetClosed: boolean;
206begin
207 result := FEasyBezier.Closed;
208end;
209
210function TBGRAPolygonalGlyph.GetMinimumDotProduct: single;
211begin
212 result := FEasyBezier.MinimumDotProduct;
213end;
214
215function TBGRAPolygonalGlyph.GetPoint(AIndex: integer): TPointF;
216begin
217 result := FEasyBezier.Point[AIndex];
218end;
219
220function TBGRAPolygonalGlyph.GetPointCount: integer;
221begin
222 result := FEasyBezier.PointCount;
223end;
224
225procedure TBGRAPolygonalGlyph.SetClosed(AValue: boolean);
226begin
227 FEasyBezier.Closed := AValue;
228end;
229
230procedure TBGRAPolygonalGlyph.SetMinimumDotProduct(AValue: single);
231begin
232 FEasyBezier.MinimumDotProduct := AValue;
233end;
234
235procedure TBGRAPolygonalGlyph.SetPoint(AIndex: integer; AValue: TPointF);
236begin
237 FEasyBezier.Point[AIndex] := AValue;
238end;
239
240procedure TBGRAPolygonalGlyph.SetQuadraticCurves(AValue: boolean);
241begin
242 if FQuadraticCurves=AValue then Exit;
243 FQuadraticCurves:=AValue;
244end;
245
246function TBGRAPolygonalGlyph.ContentSize: integer;
247begin
248 Result:= (inherited ContentSize) + sizeof(single)*2 + 4 + sizeof(single)*2*PointCount;
249end;
250
251function TBGRAPolygonalGlyph.HeaderName: string;
252begin
253 if FQuadraticCurves then
254 Result:='TBGRAEasyBezierGlyph'
255 else
256 Result:='TBGRAPolygonalGlyph'
257end;
258
259procedure TBGRAPolygonalGlyph.WriteContent(AStream: TStream);
260var i: integer;
261begin
262 inherited WriteContent(AStream);
263 LEWritePointF(AStream, Offset);
264 LEWriteLongint(AStream,PointCount);
265 for i := 0 to PointCount-1 do
266 LEWritePointF(AStream, FEasyBezier.Point[i]);
267 if FQuadraticCurves then
268 for i := 0 to PointCount-1 do
269 LEWriteLongint(AStream, ord(FEasyBezier.CurveMode[i]));
270end;
271
272procedure TBGRAPolygonalGlyph.ReadContent(AStream: TStream);
273var i: integer;
274 tempPts: array of TPointF;
275 flags: LongInt;
276begin
277 inherited ReadContent(AStream);
278 Offset := LEReadPointF(AStream);
279 SetLength(tempPts, LEReadLongint(AStream));
280 for i := 0 to high(tempPts) do
281 tempPts[i] := LEReadPointF(AStream);
282 SetPoints(tempPts);
283 if FQuadraticCurves then
284 begin
285 for i := 0 to high(tempPts) do
286 begin
287 flags := LEReadLongint(AStream);
288 FEasyBezier.CurveMode[i] := TEasyBezierCurveMode(flags and 255);
289 end;
290 end;
291end;
292
293function TBGRAPolygonalGlyph.PointTransformMatrix(APoint: PPointF;
294 AData: pointer): TPointF;
295begin
296 result := TAffineMatrix(AData^) * APoint^;
297end;
298
299procedure TBGRAPolygonalGlyph.Init;
300begin
301 FEasyBezier.Init;
302 Closed := True;
303 Offset := PointF(0,0);
304 FQuadraticCurves:= False;
305end;
306
307constructor TBGRAPolygonalGlyph.Create(AIdentifier: string);
308begin
309 Init;
310 inherited Create(AIdentifier);
311end;
312
313constructor TBGRAPolygonalGlyph.Create(AStream: TStream);
314begin
315 Init;
316 inherited Create(AStream);
317end;
318
319constructor TBGRAPolygonalGlyph.Create(AStream: TStream; AQuadratic: boolean);
320begin
321 Init;
322 FQuadraticCurves:= AQuadratic;
323 inherited Create(AStream);
324end;
325
326procedure TBGRAPolygonalGlyph.SetPoints(const APoints: array of TPointF);
327begin
328 FEasyBezier.SetPoints(APoints, cmAuto);
329end;
330
331procedure TBGRAPolygonalGlyph.SetPoints(const APoints: array of TPointF;
332 const ACurveMode: array of TGlyphPointCurveMode);
333begin
334 if length(APoints) <> length(ACurveMode) then
335 raise exception.Create('Dimension mismatch');
336 FEasyBezier.SetPoints(APoints, ACurveMode);
337end;
338
339procedure TBGRAPolygonalGlyph.Path(ADest: IBGRAPath; AMatrix: TAffineMatrix);
340var i: integer;
341 nextMove: boolean;
342begin
343 AMatrix := AMatrix*AffineMatrixTranslation(Offset.X,Offset.Y);
344 if not FQuadraticCurves then
345 begin
346 nextMove := true;
347 for i := 0 to PointCount-1 do
348 if isEmptyPointF(Point[i]) then
349 begin
350 if not nextMove and Closed then ADest.closePath;
351 nextMove := true;
352 end else
353 begin
354 if nextMove then
355 begin
356 ADest.moveTo(AMatrix*Point[i]);
357 nextMove := false;
358 end else
359 ADest.lineTo(AMatrix*Point[i]);
360 end;
361 if not nextmove and Closed then ADest.closePath;
362 end else
363 FEasyBezier.CopyToPath(ADest, @PointTransformMatrix, @AMatrix);
364end;
365
366{ TBGRAGlyph }
367
368procedure TBGRAGlyph.WriteHeader(AStream: TStream; AName: string;
369 AContentSize: longint);
370begin
371 LEWriteByte(AStream, length(AName));
372 AStream.Write(AName[1],length(AName));
373 LEWriteLongint(AStream, AContentSize);
374end;
375
376class procedure TBGRAGlyph.ReadHeader(AStream: TStream; out AName: string; out
377 AContentSize: longint);
378var NameLength: integer;
379begin
380 NameLength := LEReadByte(AStream);
381 setlength(AName,NameLength);
382 AStream.Read(AName[1],length(AName));
383 AContentSize := LEReadLongint(AStream);
384end;
385
386function TBGRAGlyph.ContentSize: integer;
387begin
388 result := 4+length(FIdentifier)+sizeof(single)*2;
389end;
390
391function TBGRAGlyph.HeaderName: string;
392begin
393 result := 'TBGRAGlyph';
394end;
395
396procedure TBGRAGlyph.WriteContent(AStream: TStream);
397begin
398 LEWriteLongint(AStream,length(FIdentifier));
399 AStream.Write(FIdentifier[1],length(FIdentifier));
400 LEWriteSingle(AStream,Width);
401 LEWriteSingle(AStream,Height);
402end;
403
404procedure TBGRAGlyph.ReadContent(AStream: TStream);
405var lIdentifierLength: integer;
406begin
407 lIdentifierLength:= LEReadLongint(AStream);
408 setlength(FIdentifier, lIdentifierLength);
409 AStream.Read(FIdentifier[1],length(FIdentifier));
410 Width := LEReadSingle(AStream);
411 Height := LEReadSingle(AStream);
412end;
413
414constructor TBGRAGlyph.Create(AIdentifier: string);
415begin
416 FIdentifier:= AIdentifier;
417end;
418
419constructor TBGRAGlyph.Create(AStream: TStream);
420begin
421 ReadContent(AStream);
422end;
423
424procedure TBGRAGlyph.Path(ADest: IBGRAPath; AMatrix: TAffineMatrix);
425begin
426 //nothing
427end;
428
429procedure TBGRAGlyph.SaveToStream(AStream: TStream);
430begin
431 WriteHeader(AStream, HeaderName, ContentSize);
432 WriteContent(AStream);
433end;
434
435class function TBGRAGlyph.LoadFromStream(AStream: TStream) : TBGRAGlyph;
436var lName: string;
437 lContentSize: integer;
438 EndPosition: Int64;
439begin
440 ReadHeader(AStream,lName,lContentSize);
441 EndPosition := AStream.Position + lContentSize;
442 if lName = 'TBGRAPolygonalGlyph' then
443 result := TBGRAPolygonalGlyph.Create(AStream)
444 else if lName = 'TBGRAEasyBezierGlyph' then
445 result := TBGRAPolygonalGlyph.Create(AStream, true)
446 else if lName = 'TBGRAGlyph' then
447 result := TBGRAGlyph.Create(AStream)
448 else
449 raise exception.Create('Unknown glyph type (' + lName + ')');
450 AStream.Position:= EndPosition;
451end;
452
453{ TBGRACustomTypeWriter }
454
455function TBGRACustomTypeWriter.GetGlyph(AIdentifier: string): TBGRAGlyph;
456var Node: TAvgLvlTreeNode;
457begin
458 Node := FindGlyph(AIdentifier);
459 if Node = nil then
460 result := nil
461 else
462 result := TBGRAGlyph(Node.Data);
463end;
464
465procedure TBGRACustomTypeWriter.SetGlyph(AIdentifier: string; AValue: TBGRAGlyph);
466var Node: TAvgLvlTreeNode;
467begin
468 if AValue.Identifier <> AIdentifier then
469 raise exception.Create('Identifier mismatch');
470 Node := FindGlyph(AIdentifier);
471 if Node <> nil then
472 begin
473 if pointer(AValue) <> Node.Data then
474 TBGRAGlyph(Node.Data).Free;
475 Node.Data := AValue;
476 end else
477 FGlyphs.Add(pointer(AValue));
478end;
479
480function TBGRACustomTypeWriter.CompareGlyph(Tree: TAvgLvlTree; Data1, Data2: Pointer): integer;
481begin
482 result := CompareStr(TBGRAGlyph(Data1).Identifier,TBGRAGlyph(Data2).Identifier);
483end;
484
485function TBGRACustomTypeWriter.FindGlyph(AIdentifier: string): TAvgLvlTreeNode;
486var Comp: integer;
487 Node: TAvgLvlTreeNode;
488begin
489 Node:=FGlyphs.Root;
490 while (Node<>nil) do begin
491 Comp:=CompareStr(AIdentifier,TBGRAGlyph(Node.Data).Identifier);
492 if Comp=0 then break;
493 if Comp<0 then begin
494 Node:=Node.Left
495 end else begin
496 Node:=Node.Right
497 end;
498 end;
499 result := Node;
500end;
501
502constructor TBGRACustomTypeWriter.Create;
503begin
504 FGlyphs := TAvgLvlTree.CreateObjectCompare(@CompareGlyph);
505 TypeWriterMatrix := AffineMatrixIdentity;
506 OutlineMode:= twoFill;
507 DrawGlyphsSimultaneously := false;
508end;
509
510procedure TBGRACustomTypeWriter.DrawGlyph(ADest: TBGRACanvas2D;
511 AIdentifier: string; X, Y: Single; AAlign: TBGRATypeWriterAlignment);
512begin
513 GlyphPath(ADest, AIdentifier, X,Y, AAlign);
514 DrawLastPath(ADest);
515end;
516
517procedure TBGRACustomTypeWriter.DrawText(ADest: TBGRACanvas2D; ATextUTF8: string;
518 X, Y: Single; AAlign: TBGRATypeWriterAlignment);
519begin
520 TextPath(ADest, ATextUTF8, X,Y, AAlign, (OutlineMode <> twoPath) and not DrawGlyphsSimultaneously);
521end;
522
523procedure TBGRACustomTypeWriter.CopyTextPathTo(ADest: IBGRAPath; ATextUTF8: string; X,Y: Single; AAlign: TBGRATypeWriterAlignment = twaTopLeft);
524var
525 pstr: pchar;
526 left,charlen: integer;
527 nextchar: string;
528 g: TBGRAGlyph;
529 m,m2: TAffineMatrix;
530begin
531 if ATextUTF8 = '' then exit;
532 m := GetTextMatrix(ATextUTF8, X,Y,AAlign);
533 m2 := m;
534
535 pstr := @ATextUTF8[1];
536 left := length(ATextUTF8);
537 while left > 0 do
538 begin
539 charlen := UTF8CharacterLength(pstr);
540 setlength(nextchar, charlen);
541 move(pstr^, nextchar[1], charlen);
542 inc(pstr,charlen);
543 dec(left,charlen);
544
545 g := GetGlyph(nextchar);
546 if g <> nil then
547 begin
548 if AAlign in [twaLeft,twaMiddle,twaRight] then
549 m2 := m*AffineMatrixTranslation(0,-g.Height/2) else
550 if AAlign in [twaBottomLeft,twaBottom,twaBottomRight] then
551 m2 := m*AffineMatrixTranslation(0,-g.Height)
552 else
553 m2 := m;
554 g.Path(ADest, m2);
555 m := m*AffineMatrixTranslation(g.Width,0);
556 end;
557 end;
558end;
559
560function TBGRACustomTypeWriter.GetGlyphBox(AIdentifier: string; X, Y: Single;
561 AAlign: TBGRATypeWriterAlignment): TAffineBox;
562var g: TBGRAGlyph;
563 m: TAffineMatrix;
564begin
565 g := GetGlyph(AIdentifier);
566 if g = nil then result := TAffineBox.EmptyBox else
567 begin
568 m := GetGlyphMatrix(g,X,Y,AAlign);
569 result := TAffineBox.AffineBox(m*PointF(0,0),m*PointF(g.Width,0),m*PointF(0,g.Height));
570 end;
571end;
572
573function TBGRACustomTypeWriter.GetTextBox(ATextUTF8: string; X, Y: Single;
574 AAlign: TBGRATypeWriterAlignment): TAffineBox;
575var
576 m: TAffineMatrix;
577 totalWidth,minY,maxY,gMinY,gMaxY: single;
578
579 pstr: pchar;
580 left,charlen: integer;
581 nextchar: string;
582 g: TBGRAGlyph;
583
584begin
585 if ATextUTF8 = '' then result := TAffineBox.EmptyBox else
586 begin
587 m := GetTextMatrix(ATextUTF8,X,Y,AAlign);
588 minY := 0;
589 maxY := 0;
590 totalWidth := 0;
591
592 pstr := @ATextUTF8[1];
593 left := length(ATextUTF8);
594 while left > 0 do
595 begin
596 charlen := UTF8CharacterLength(pstr);
597 setlength(nextchar, charlen);
598 move(pstr^, nextchar[1], charlen);
599 inc(pstr,charlen);
600 dec(left,charlen);
601
602 g := GetGlyph(nextchar);
603 if g <> nil then
604 begin
605 totalWidth += g.Width;
606
607 if AAlign in [twaLeft,twaMiddle,twaRight] then
608 begin
609 gMinY := -g.Height/2;
610 gMaxY := g.Height/2;
611 end else
612 if AAlign in [twaBottomLeft,twaBottom,twaBottomRight] then
613 begin
614 gMinY := -g.Height;
615 gMaxY := 0;
616 end
617 else
618 begin
619 gMinY := 0;
620 gMaxY := g.Height;
621 end;
622 if gMinY < minY then minY := gMinY;
623 if gMaxY > maxY then maxY := gMaxY;
624 end;
625 end;
626
627 result := TAffineBox.AffineBox(m*PointF(0,minY),m*PointF(totalWidth,minY),m*PointF(0,maxY));
628 end;
629end;
630
631function TBGRACustomTypeWriter.GetTextGlyphBoxes(ATextUTF8: string; X, Y: Single;
632 AAlign: TBGRATypeWriterAlignment): TGlyphBoxes;
633var
634 m: TAffineMatrix;
635 gMinY,gMaxY: single;
636
637 pstr: pchar;
638 left,charlen: integer;
639 nextchar: string;
640 g: TBGRAGlyph;
641 numChar: integer;
642
643begin
644 if ATextUTF8 = '' then result := nil else
645 begin
646 setlength(result, UTF8Length(ATextUTF8));
647
648 m := GetTextMatrix(ATextUTF8,X,Y,AAlign);
649
650 pstr := @ATextUTF8[1];
651 left := length(ATextUTF8);
652 numChar := 0;
653 while left > 0 do
654 begin
655 charlen := UTF8CharacterLength(pstr);
656 setlength(nextchar, charlen);
657 move(pstr^, nextchar[1], charlen);
658 inc(pstr,charlen);
659 dec(left,charlen);
660
661 result[numChar].Glyph := nextchar;
662 g := GetGlyph(nextchar);
663 if g <> nil then
664 begin
665 if AAlign in [twaLeft,twaMiddle,twaRight] then
666 begin
667 gMinY := -g.Height/2;
668 gMaxY := g.Height/2;
669 end else
670 if AAlign in [twaBottomLeft,twaBottom,twaBottomRight] then
671 begin
672 gMinY := -g.Height;
673 gMaxY := 0;
674 end
675 else
676 begin
677 gMinY := 0;
678 gMaxY := g.Height;
679 end;
680 result[numChar].Box := TAffineBox.AffineBox(m*PointF(0,gMinY),m*PointF(g.Width,gMinY),m*PointF(0,gMaxY));
681 m := m*AffineMatrixTranslation(g.Width,0);
682 end else
683 result[numChar].Box := TAffineBox.EmptyBox;
684
685 inc(numChar);
686 end;
687 end;
688end;
689
690procedure TBGRACustomTypeWriter.NeedGlyphRange(AUnicodeFrom, AUnicodeTo: Cardinal);
691var c: cardinal;
692begin
693 for c := AUnicodeFrom to AUnicodeTo do
694 GetGlyph(UnicodeCharToUTF8(c));
695end;
696
697procedure TBGRACustomTypeWriter.NeedGlyphAnsiRange;
698var i: integer;
699begin
700 for i := 0 to 255 do
701 GetGlyph(AnsiToUtf8(chr(i)));
702end;
703
704procedure TBGRACustomTypeWriter.TextPath(ADest: TBGRACanvas2D; ATextUTF8: string; X,
705 Y: Single; AAlign: TBGRATypeWriterAlignment; ADrawEachChar: boolean);
706var
707 pstr: pchar;
708 left,charlen: integer;
709 nextchar: string;
710 g: TBGRAGlyph;
711 m,m2: TAffineMatrix;
712begin
713 if not ADrawEachChar then ADest.beginPath;
714 if ATextUTF8 = '' then exit;
715 m := GetTextMatrix(ATextUTF8, X,Y,AAlign);
716 m2 := m;
717
718 pstr := @ATextUTF8[1];
719 left := length(ATextUTF8);
720 while left > 0 do
721 begin
722 charlen := UTF8CharacterLength(pstr);
723 setlength(nextchar, charlen);
724 move(pstr^, nextchar[1], charlen);
725 inc(pstr,charlen);
726 dec(left,charlen);
727
728 g := GetGlyph(nextchar);
729 if g <> nil then
730 begin
731 if AAlign in [twaLeft,twaMiddle,twaRight] then
732 m2 := m*AffineMatrixTranslation(0,-g.Height/2) else
733 if AAlign in [twaBottomLeft,twaBottom,twaBottomRight] then
734 m2 := m*AffineMatrixTranslation(0,-g.Height)
735 else
736 m2 := m;
737 if ADrawEachChar then ADest.beginPath;
738 g.Path(ADest, m2);
739 if ADrawEachChar then DrawLastPath(ADest);
740 m := m*AffineMatrixTranslation(g.Width,0);
741 end;
742 end;
743end;
744
745procedure TBGRACustomTypeWriter.GlyphPath(ADest: TBGRACanvas2D; AIdentifier: string;
746 X, Y: Single; AAlign: TBGRATypeWriterAlignment);
747var g: TBGRAGlyph;
748begin
749 ADest.beginPath;
750 g := GetGlyph(AIdentifier);
751 if g = nil then exit;
752 g.Path(ADest, GetGlyphMatrix(g,X,Y,AAlign));
753end;
754
755procedure TBGRACustomTypeWriter.DrawLastPath(ADest: TBGRACanvas2D);
756begin
757 case OutlineMode of
758 twoPath: ;
759 twoFill: ADest.fill;
760 twoStroke: ADest.stroke;
761 twoFillOverStroke: ADest.fillOverStroke;
762 twoStrokeOverFill: ADest.strokeOverFill;
763 twoFillThenStroke: begin ADest.fill; ADest.stroke; end;
764 twoStrokeThenFill: begin ADest.stroke; ADest.fill; end;
765 end;
766end;
767
768procedure TBGRACustomTypeWriter.ClearGlyphs;
769begin
770 FGlyphs.FreeAndClear;
771end;
772
773procedure TBGRACustomTypeWriter.RemoveGlyph(AIdentifier: string);
774var Node: TAvgLvlTreeNode;
775begin
776 Node := FindGlyph(AIdentifier);
777 if Node <> nil then FGlyphs.Delete(Node);
778end;
779
780procedure TBGRACustomTypeWriter.AddGlyph(AGlyph: TBGRAGlyph);
781begin
782 Glyph[AGlyph.Identifier] := AGlyph;
783end;
784
785procedure TBGRACustomTypeWriter.SaveGlyphsToStream(AStream: TStream);
786var Enumerator: TAvgLvlTreeNodeEnumerator;
787begin
788 LEWriteLongint(AStream,CustomHeaderSize);
789 WriteCustomHeader(AStream);
790
791 Enumerator := FGlyphs.GetEnumerator;
792 while Enumerator.MoveNext do
793 TBGRAGlyph(Enumerator.Current.Data).SaveToStream(AStream);
794 Enumerator.Free;
795end;
796
797procedure TBGRACustomTypeWriter.LoadGlyphsFromFile(AFilenameUTF8: string);
798var Stream: TFileStreamUTF8;
799begin
800 Stream := nil;
801 try
802 Stream := TFileStreamUTF8.Create(AFilenameUTF8, fmOpenRead);
803 LoadGlyphsFromStream(Stream);
804 finally
805 Stream.Free;
806 end;
807end;
808
809procedure TBGRACustomTypeWriter.LoadGlyphsFromStream(AStream: TStream);
810var Header: TBGRACustomTypeWriterHeader;
811 i: integer;
812 g: TBGRAGlyph;
813 HeaderSize: integer;
814 GlyphStartPosition: Int64;
815begin
816 HeaderSize := LEReadLongint(AStream);
817 GlyphStartPosition:= AStream.Position+HeaderSize;
818 Header := ReadCustomTypeWriterHeader(AStream);
819 if header.HeaderName <> HeaderName then
820 raise exception.Create('Invalid file format ("'+header.HeaderName+'" should be "'+HeaderName+'")');
821 ReadAdditionalHeader(AStream);
822 AStream.Position:= GlyphStartPosition;
823 for i := 0 to Header.NbGlyphs-1 do
824 begin
825 g := TBGRAGlyph.LoadFromStream(AStream);
826 AddGlyph(g);
827 end;
828end;
829
830procedure TBGRACustomTypeWriter.SaveGlyphsToFile(AFilenameUTF8: string);
831var Stream: TFileStreamUTF8;
832begin
833 Stream := nil;
834 try
835 Stream := TFileStreamUTF8.Create(AFilenameUTF8, fmCreate or fmOpenWrite);
836 SaveGlyphsToStream(Stream);
837 finally
838 Stream.Free;
839 end;
840end;
841
842function TBGRACustomTypeWriter.GetGlyphMatrix(AGlyph: TBGRAGlyph; X, Y: Single;
843 AAlign: TBGRATypeWriterAlignment): TAffineMatrix;
844var tGlyph: TPointF;
845begin
846 if AGlyph = nil then
847 begin
848 result := AffineMatrixIdentity;
849 exit;
850 end;
851 tGlyph := PointF(0,0);
852 if AAlign in [twaTop,twaMiddle,twaBottom] then tGlyph.X -= AGlyph.Width/2;
853 if AAlign in [twaTopRight,twaRight,twaBottomRight] then tGlyph.X -= AGlyph.Width;
854 if AAlign in [twaLeft,twaMiddle,twaRight] then tGlyph.Y -= AGlyph.Height/2;
855 if AAlign in [twaBottomLeft,twaBottom,twaBottomRight] then tGlyph.Y -= AGlyph.Height;
856 result := AffineMatrixTranslation(X,Y)*TypeWriterMatrix*AffineMatrixTranslation(tGlyph.X,tGlyph.Y);
857end;
858
859function TBGRACustomTypeWriter.GetTextMatrix(ATextUTF8: string; X, Y: Single;
860 AAlign: TBGRATypeWriterAlignment): TAffineMatrix;
861var
862 tGlyph: TPointF;
863 totalWidth: single;
864 pstr: pchar;
865 left,charlen: integer;
866 nextchar: string;
867 g: TBGRAGlyph;
868begin
869 tGlyph := PointF(0,0);
870 if not (AAlign in [twaLeft,twaTopLeft,twaBottomLeft]) then
871 begin
872 totalWidth := 0;
873 pstr := @ATextUTF8[1];
874 left := length(ATextUTF8);
875 while left > 0 do
876 begin
877 charlen := UTF8CharacterLength(pstr);
878 setlength(nextchar, charlen);
879 move(pstr^, nextchar[1], charlen);
880 inc(pstr,charlen);
881 dec(left,charlen);
882
883 g := GetGlyph(nextchar);
884 if g <> nil then totalWidth += g.Width;
885 end;
886
887 if AAlign in[twaTop,twaMiddle,twaBottom] then tGlyph.X -= totalWidth/2 else
888 if AAlign in[twaTopRight, twaRight, twaBottomRight] then tGlyph.X -= totalWidth;
889 end;
890 result := AffineMatrixTranslation(X,Y)*TypeWriterMatrix*AffineMatrixTranslation(tGlyph.X,tGlyph.Y);
891end;
892
893function TBGRACustomTypeWriter.CustomHeaderSize: integer;
894begin
895 result := 1+length(HeaderName)+4;
896end;
897
898procedure TBGRACustomTypeWriter.WriteCustomHeader(AStream: TStream);
899var lHeaderName: string;
900begin
901 lHeaderName:= HeaderName;
902 LEWriteByte(AStream,length(lHeaderName));
903 AStream.Write(lHeaderName[1],length(lHeaderName));
904 LEWriteLongint(AStream,FGlyphs.Count);
905end;
906
907function TBGRACustomTypeWriter.ReadCustomTypeWriterHeader(AStream: TStream
908 ): TBGRACustomTypeWriterHeader;
909begin
910 setlength(result.HeaderName, LEReadByte(AStream));
911 AStream.Read(result.HeaderName[1],length(result.HeaderName));
912 result.NbGlyphs:= LEReadLongint(AStream);
913end;
914
915procedure TBGRACustomTypeWriter.ReadAdditionalHeader(AStream: TStream);
916begin
917 //nothing
918end;
919
920function TBGRACustomTypeWriter.HeaderName: string;
921begin
922 result := 'TBGRACustomTypeWriter';
923end;
924
925destructor TBGRACustomTypeWriter.Destroy;
926begin
927 FGlyphs.FreeAndClear;
928 FGlyphs.Free;
929 inherited Destroy;
930end;
931
932end.
933
Note: See TracBrowser for help on using the repository browser.