source: trunk/Packages/Graphics32/GR32_Text_LCL_Win.pas

Last change on this file was 2, checked in by chronos, 5 years ago
File size: 15.8 KB
Line 
1unit GR32_Text_LCL_Win;
2
3(* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1 or LGPL 2.1 with linking exception
5 *
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
10 *
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
15 *
16 * Alternatively, the contents of this file may be used under the terms of the
17 * Free Pascal modified version of the GNU Lesser General Public License
18 * Version 2.1 (the "FPC modified LGPL License"), in which case the provisions
19 * of this license are applicable instead of those above.
20 * Please see the file LICENSE.txt for additional information concerning this
21 * license.
22 *
23 * The Original Code is Vectorial Polygon Rasterizer for Graphics32
24 *
25 * The Initial Developer of the Original Code is
26 * Mattias Andersson <mattias@centaurix.com>
27 *
28 * Portions created by the Initial Developer are Copyright (C) 2012
29 * the Initial Developer. All Rights Reserved.
30 *
31 * Contributor(s):
32 *
33 * ***** END LICENSE BLOCK ***** *)
34
35interface
36
37{$I GR32.inc}
38
39uses
40 Windows, Types, GR32, GR32_Paths;
41
42procedure TextToPath(Font: HFONT; Path: TCustomPath;
43 const ARect: TFloatRect; const Text: WideString; Flags: Cardinal); overload;
44
45function MeasureTextDC(DC: HDC; const ARect: TFloatRect; const Text: WideString;
46 Flags: Cardinal): TFloatRect; overload;
47function MeasureText(Font: HFONT; const ARect: TFloatRect; const Text: WideString;
48 Flags: Cardinal): TFloatRect;
49
50type
51 TTextHinting = (thNone, thNoHorz, thHinting);
52
53procedure SetHinting(Value: TTextHinting);
54function GetHinting: TTextHinting;
55
56const
57 DT_LEFT = 0; //See also Window's DrawText() flags ...
58 DT_CENTER = 1; //http://msdn.microsoft.com/en-us/library/ms901121.aspx
59 DT_RIGHT = 2;
60 DT_WORDBREAK = $10;
61 DT_VCENTER = 4;
62 DT_BOTTOM = 8;
63 DT_SINGLELINE = $20;
64 DT_JUSTIFY = 3; //Graphics32 additions ...
65 DT_HORZ_ALIGN_MASK = 3;
66
67implementation
68
69uses
70 GR32_LowLevel;
71
72var
73 UseHinting: Boolean;
74 HorzStretch: Integer; // stretching factor when calling GetGlyphOutline()
75 HorzStretch_Inv: single;
76
77 VertFlip_mat2: tmat2;
78
79const
80 GGO_UNHINTED = $0100;
81 GGODefaultFlags: array [Boolean] of Integer = (GGO_NATIVE or GGO_UNHINTED, GGO_NATIVE);
82
83 TT_PRIM_CSPLINE = 3;
84
85 MaxSingle = 3.4e+38;
86
87function PointFXtoPointF(const Point: tagPointFX): TFloatPoint;
88begin
89 Result.X := Point.X.Value + Point.X.Fract * FixedToFloat;
90 Result.Y := Point.Y.Value + Point.Y.Fract * FixedToFloat;
91end;
92
93
94function GlyphOutlineToPath(Handle: HDC; Path: TCustomPath;
95 DstX, MaxX, DstY: Single;
96 const Glyph: Integer; out Metrics: TGlyphMetrics): Boolean;
97var
98 I, K, S: Integer;
99 Res: DWORD;
100 GlyphMemPtr, BufferPtr: PTTPolygonHeader;
101 CurvePtr: PTTPolyCurve;
102 P1, P2, P3: TFloatPoint;
103begin
104 Res := GetGlyphOutlineW(Handle, Glyph, GGODefaultFlags[UseHinting], Metrics,
105 0, nil, VertFlip_mat2);
106 Result := DstX + Metrics.gmCellIncX <= MaxX;
107 if not Result or not Assigned(Path) then Exit;
108
109 GetMem(GlyphMemPtr, Res);
110 BufferPtr := GlyphMemPtr;
111
112 Res := GetGlyphOutlineW(Handle, Glyph, GGODefaultFlags[UseHinting], Metrics,
113 Res, BufferPtr, VertFlip_mat2);
114
115 if (Res = GDI_ERROR) or (BufferPtr^.dwType <> TT_POLYGON_TYPE) then
116 begin
117 FreeMem(GlyphMemPtr);
118 Exit;
119 end;
120
121 while Res > 0 do
122 begin
123 S := BufferPtr.cb - SizeOf(TTTPolygonHeader);
124 PtrUInt(CurvePtr) := PtrUInt(BufferPtr) + SizeOf(TTTPolygonHeader);
125 P1 := PointFXtoPointF(BufferPtr.pfxStart);
126 Path.MoveTo(P1.X + DstX, P1.Y + DstY);
127 while S > 0 do
128 begin
129 case CurvePtr.wType of
130 TT_PRIM_LINE:
131 for I := 0 to CurvePtr.cpfx - 1 do
132 begin
133 P1 := PointFXtoPointF(CurvePtr.apfx[I]);
134 Path.LineTo(P1.X + DstX, P1.Y + DstY);
135 end;
136 TT_PRIM_QSPLINE:
137 begin
138 for I := 0 to CurvePtr.cpfx - 2 do
139 begin
140 P1 := PointFXtoPointF(CurvePtr.apfx[I]);
141 if I < CurvePtr.cpfx - 2 then
142 with PointFXtoPointF(CurvePtr.apfx[I + 1]) do
143 begin
144 P2.x := (P1.x + x) * 0.5;
145 P2.y := (P1.y + y) * 0.5;
146 end
147 else
148 P2 := PointFXtoPointF(CurvePtr.apfx[I + 1]);
149 Path.ConicTo(P1.X + DstX, P1.Y + DstY, P2.X + DstX, P2.Y + DstY);
150 end;
151 end;
152 TT_PRIM_CSPLINE:
153 begin
154 I := 0;
155 while I < CurvePtr.cpfx - 2 do
156 begin
157 P1 := PointFXtoPointF(CurvePtr.apfx[I]);
158 P2 := PointFXtoPointF(CurvePtr.apfx[I + 1]);
159 P3 := PointFXtoPointF(CurvePtr.apfx[I + 2]);
160 Path.CurveTo(P1.X + DstX, P1.Y + DstY, P2.X + DstX, P2.Y + DstY,
161 P3.X + DstX, P3.Y + DstY);
162 Inc(I, 2);
163 end;
164 end;
165 end;
166 K := (CurvePtr.cpfx - 1) * SizeOf(TPointFX) + SizeOf(TTPolyCurve);
167 Dec(S, K);
168
169 Inc(PtrInt(CurvePtr), K);
170 end;
171
172 Path.EndPath(True);
173
174 Dec(Res, BufferPtr.cb);
175 Inc(PtrInt(BufferPtr), BufferPtr.cb);
176 end;
177
178 FreeMem(GlyphMemPtr);
179end;
180
181procedure InternalTextToPath(DC: HDC; Path: TCustomPath; const ARect: TFloatRect;
182 const Text: WideString; Flags: Cardinal);
183const
184 CHAR_CR = 10;
185 CHAR_NL = 13;
186 CHAR_SP = 32;
187var
188 GlyphMetrics: TGlyphMetrics;
189 TextMetric: TTextMetric;
190 I, J, TextLen, SpcCount, SpcX, LineStart: Integer;
191 CharValue: Integer;
192 CharOffsets: TArrayOfInteger;
193 X, Y, XMax, YMax, MaxRight: Single;
194 S: WideString;
195 TextPath: TFlattenedPath;
196 OwnedPath: TFlattenedPath;
197{$IFDEF USEKERNING}
198 LastCharValue: Integer;
199 KerningPairs: PKerningPairArray;
200 KerningPairCount: Integer;
201{$ENDIF}
202
203 procedure AlignTextCenter(CurrentI: Integer);
204 var
205 M, N, PathStart, PathEnd: Integer;
206 Delta: TFloat;
207 begin
208 Delta := Round((ARect.Right * HorzStretch - X - 1) * 0.5);
209 PathStart := CharOffsets[LineStart];
210 PathEnd := CharOffsets[CurrentI];
211 for M := PathStart to PathEnd - 1 do
212 for N := 0 to High(TextPath.Path[M]) do
213 TextPath.Path[M][N].X := TextPath.Path[M][N].X + Delta;
214 end;
215
216 procedure AlignTextRight(CurrentI: Integer);
217 var
218 M, N, PathStart, PathEnd: Integer;
219 Delta: TFloat;
220 begin
221 Delta := Round(ARect.Right * HorzStretch - X - 1);
222 PathStart := CharOffsets[LineStart];
223 PathEnd := CharOffsets[CurrentI];
224 for M := PathStart to PathEnd - 1 do
225 for N := 0 to High(TextPath.Path[M]) do
226 TextPath.Path[M][N].X := TextPath.Path[M][N].X + Delta;
227 end;
228
229 procedure AlignTextJustify(CurrentI: Integer);
230 var
231 L, M, N, PathStart, PathEnd: Integer;
232 SpcDelta, SpcDeltaInc: TFloat;
233 begin
234 if (SpcCount < 1) or (Ord(Text[CurrentI]) = CHAR_CR) then
235 Exit;
236 SpcDelta := (ARect.Right * HorzStretch - X - 1) / SpcCount;
237 SpcDeltaInc := SpcDelta;
238 L := LineStart;
239 //Trim leading spaces ...
240 while (L < CurrentI) and (Ord(Text[L]) = CHAR_SP) do Inc(L);
241 //Now find first space char in line ...
242 while (L < CurrentI) and (Ord(Text[L]) <> CHAR_SP) do Inc(L);
243 PathStart := CharOffsets[L - 1];
244 repeat
245 M := L + 1;
246 while (M < CurrentI) and (Ord(Text[M]) <> CHAR_SP) do Inc(M);
247 PathEnd := CharOffsets[M];
248 L := M;
249 for M := PathStart to PathEnd - 1 do
250 for N := 0 to High(TextPath.Path[M]) do
251 TextPath.Path[M][N].X := TextPath.Path[M][N].X + SpcDeltaInc;
252 SpcDeltaInc := SpcDeltaInc + SpcDelta;
253 PathStart := PathEnd;
254 until L >= CurrentI;
255 end;
256
257 procedure NewLine(CurrentI: Integer);
258 begin
259 if (Flags and DT_SINGLELINE <> 0) then Exit;
260 if assigned(TextPath) then
261 case (Flags and DT_HORZ_ALIGN_MASK) of
262 DT_CENTER : AlignTextCenter(CurrentI);
263 DT_RIGHT : AlignTextRight(CurrentI);
264 DT_JUSTIFY: AlignTextJustify(CurrentI);
265 end;
266 X := ARect.Left * HorzStretch;
267 Y := Y + TextMetric.tmHeight;
268 LineStart := CurrentI;
269 SpcCount := 0;
270 end;
271
272 function MeasureTextX(const S: WideString): Integer;
273 var
274 I: Integer;
275 begin
276 Result := 0;
277 for I := 1 to Length(S) do
278 begin
279 CharValue := Ord(S[I]);
280 GetGlyphOutlineW(DC, CharValue,
281 GGODefaultFlags[UseHinting], GlyphMetrics, 0, nil, VertFlip_mat2);
282 Inc(Result, GlyphMetrics.gmCellIncX);
283 end;
284 end;
285
286 function NeedsNewLine(X: Single): boolean;
287 begin
288 Result := X > ARect.Right * HorzStretch;
289 end;
290
291 procedure AddSpace;
292 begin
293 Inc(SpcCount);
294 X := X + SpcX;
295 end;
296
297begin
298{$IFDEF USEKERNING}
299 KerningPairs := nil;
300 KerningPairCount := GetKerningPairs(DC, 0, nil);
301 if GetLastError <> 0 then
302 RaiseLastOSError;
303 if KerningPairCount > 0 then
304 begin
305 GetMem(KerningPairs, KerningPairCount * SizeOf(TKerningPair));
306 GetKerningPairs(DC, KerningPairCount, PKerningPair(KerningPairs));
307 end;
308 LastCharValue := 0;
309{$ENDIF}
310 SpcCount := 0;
311 LineStart := 0;
312 OwnedPath := nil;
313 if (Path <> nil) then
314 begin
315 if (Path is TFlattenedPath) then
316 begin
317 TextPath := TFlattenedPath(Path);
318 TextPath.Clear;
319 end
320 else
321 begin
322 OwnedPath := TFlattenedPath.Create;
323 TextPath := OwnedPath;
324 end
325 end else
326 TextPath := nil;
327
328 GetTextMetrics(DC, TextMetric);
329 TextLen := Length(Text);
330 X := ARect.Left * HorzStretch;
331 Y := ARect.Top + TextMetric.tmAscent;
332 XMax := X;
333
334 if not Assigned(Path) or (ARect.Right = ARect.Left) then
335 MaxRight := MaxSingle //either measuring text or unbounded text
336 else
337 MaxRight := ARect.Right * HorzStretch;
338 SetLength(CharOffsets, TextLen +1);
339 CharOffsets[0] := 0;
340
341 GetGlyphOutlineW(DC, CHAR_SP, GGODefaultFlags[UseHinting],
342 GlyphMetrics, 0, nil, VertFlip_mat2);
343 SpcX := GlyphMetrics.gmCellIncX;
344
345 if (Flags and DT_SINGLELINE <> 0) then
346 begin
347 //ignore justify when forcing singleline ...
348 if (Flags and DT_JUSTIFY = DT_JUSTIFY) then
349 Flags := Flags and not DT_JUSTIFY;
350 //ignore wordbreak when forcing singleline ...
351 if (Flags and DT_WORDBREAK = DT_WORDBREAK) then
352 Flags := Flags and not DT_WORDBREAK;
353 MaxRight := MaxSingle;
354 end;
355
356 // Batch whole path construction so we can be sure that the path isn't rendered
357 // while we're still modifying it.
358 if (TextPath <> nil) then
359 TextPath.BeginUpdate;
360
361 for I := 1 to TextLen do
362 begin
363 CharValue := Ord(Text[I]);
364 if CharValue <= 32 then
365 begin
366 if (Flags and DT_SINGLELINE = DT_SINGLELINE) then
367 CharValue := CHAR_SP;
368 if Assigned(TextPath) then
369 CharOffsets[I] := Length(TextPath.Path);
370
371 case CharValue of
372 CHAR_CR: NewLine(I);
373 CHAR_NL: ;
374 CHAR_SP:
375 begin
376 if Flags and DT_WORDBREAK = DT_WORDBREAK then
377 begin
378 J := I + 1;
379 while (J <= TextLen) and
380 ([Ord(Text[J])] * [CHAR_CR, CHAR_NL, CHAR_SP] = []) do
381 Inc(J);
382 S := Copy(Text, I, J - I);
383 if NeedsNewLine(X + MeasureTextX(S)) then
384 NewLine(I) else
385 AddSpace;
386 end else
387 begin
388 if NeedsNewLine(X + SpcX) then
389 NewLine(I)
390 else
391 AddSpace;
392 end;
393 end;
394 end;
395 end
396 else
397 begin
398 if GlyphOutlineToPath(DC, TextPath,
399 X, MaxRight, Y, CharValue, GlyphMetrics) then
400 begin
401 if Assigned(TextPath) then
402 CharOffsets[I] := Length(TextPath.Path);
403 end else
404 begin
405 if Ord(Text[I -1]) = CHAR_SP then
406 begin
407 //this only happens without DT_WORDBREAK
408 X := X - SpcX;
409 Dec(SpcCount);
410 end;
411 //the current glyph doesn't fit so a word must be split since
412 //it fills more than a whole line ...
413 NewLine(I - 1);
414 if not GlyphOutlineToPath(DC, TextPath,
415 X, MaxRight, Y, CharValue, GlyphMetrics) then Break;
416 if Assigned(TextPath) then
417 CharOffsets[I] := Length(TextPath.Path);
418 end;
419 X := X + GlyphMetrics.gmCellIncX;
420 {$IFDEF USEKERNING}
421 for J := 0 to KerningPairCount - 1 do
422 begin
423 if (KerningPairs^[J].wFirst = LastCharValue) and
424 (KerningPairs^[J].wSecond = CharValue) then
425 X := X + KerningPairs^[J].iKernAmount;
426 end;
427 LastCharValue := CharValue;
428 {$ENDIF}
429 if X > XMax then XMax := X;
430 end;
431 end;
432 if [(Flags and DT_HORZ_ALIGN_MASK)] * [DT_CENTER, DT_RIGHT] <> [] then
433 NewLine(TextLen);
434
435 YMax := Y + TextMetric.tmHeight - TextMetric.tmAscent;
436 //reverse HorzStretch (if any) ...
437 if (HorzStretch <> 1) and assigned(TextPath) then
438 for I := 0 to High(TextPath.Path) do
439 for J := 0 to High(TextPath.Path[I]) do
440 TextPath.Path[I][J].X := TextPath.Path[I][J].X * HorzStretch_Inv;
441 XMax := XMax * HorzStretch_Inv;
442
443 X := ARect.Right - XMax;
444 Y := ARect.Bottom - YMax;
445 if Flags and (DT_VCENTER or DT_BOTTOM) <> 0 then
446 begin
447 if Flags and DT_VCENTER <> 0 then
448 Y := Y * 0.5;
449 if assigned(TextPath) then
450 for I := 0 to High(TextPath.Path) do
451 for J := 0 to High(TextPath.Path[I]) do
452 TextPath.Path[I][J].Y := TextPath.Path[I][J].Y + Y;
453 end;
454
455{$IFDEF USEKERNING}
456 if Assigned(KerningPairs) then
457 FreeMem(KerningPairs);
458{$ENDIF}
459
460 if (Path <> nil) then
461 begin
462 TextPath.EndPath; // TODO : Why is this needed?
463
464 if (Path <> TextPath) then
465 Path.Assign(TextPath);
466
467 TextPath.EndUpdate;
468
469 OwnedPath.Free;
470 end;
471end;
472
473procedure TextToPath(Font: HFONT; Path: TCustomPath; const ARect: TFloatRect;
474 const Text: WideString; Flags: Cardinal); overload;
475var
476 DC: HDC;
477 SavedFont: HFONT;
478begin
479 DC := GetDC(0);
480 try
481 SavedFont := SelectObject(DC, Font);
482 InternalTextToPath(DC, Path, ARect, Text, Flags);
483 SelectObject(DC, SavedFont);
484 finally
485 ReleaseDC(0, DC);
486 end;
487end;
488
489function MeasureTextDC(DC: HDC; const ARect: TFloatRect; const Text: WideString;
490 Flags: Cardinal): TFloatRect;
491begin
492 Result := ARect;
493 InternalTextToPath(DC, nil, Result, Text, Flags);
494 Result.Left := Round(Result.Left);
495 Result.Top := Round(Result.Top);
496 Result.Right := Round(Result.Right);
497 Result.Bottom := Round(Result.Bottom);
498end;
499
500function MeasureText(Font: HFONT; const ARect: TFloatRect;
501 const Text: WideString; Flags: Cardinal): TFloatRect;
502var
503 DC: HDC;
504 SavedFont: HFONT;
505begin
506 DC := GetDC(0);
507 try
508 SavedFont := SelectObject(DC, Font);
509 Result := MeasureTextDC(DC, ARect, Text, Flags);
510 SelectObject(DC, SavedFont);
511 finally
512 ReleaseDC(0, DC);
513 end;
514end;
515
516procedure SetHinting(Value: TTextHinting);
517begin
518 UseHinting := Value <> thNone;
519 if (Value = thNoHorz) then
520 HorzStretch := 16 else
521 HorzStretch := 1;
522 HorzStretch_Inv := 1 / HorzStretch;
523 FillChar(VertFlip_mat2, SizeOf(VertFlip_mat2), 0);
524 VertFlip_mat2.eM11.value := HorzStretch;
525 VertFlip_mat2.eM22.value := -1; //reversed Y axis
526end;
527
528function GetHinting: TTextHinting;
529begin
530 if HorzStretch <> 1 then Result := thNoHorz
531 else if UseHinting then Result := thHinting
532 else Result := thNone;
533end;
534
535procedure InitHinting;
536begin
537{$IFDEF NOHORIZONTALHINTING}
538 SetHinting(thNoHorz);
539{$ELSE}
540 {$IFDEF NOHINTING}
541 SetHinting(thNone);
542 {$ELSE}
543 SetHinting(thHinting);
544 {$ENDIF};
545{$ENDIF}
546end;
547
548initialization
549 InitHinting;
550
551end.
Note: See TracBrowser for help on using the repository browser.