(4)DoDrawLayout
DoDrawLayout函数的源代码分析如下:
procedure TTextLayoutNG.DoDrawLayout(const ACanvas: TCanvas); var CharDic: TCharDic; Rec: PCharRec; Pos: TPointF; R, SrcR, ClipBounds: TRectF; LLine: TGPULine; LRun: TGPURun; I, J, K: Integer; VerticalAligned, HorizontalAligned, ColoredGlyph: Boolean; Styles: TFontStyles; Thickness: Single; begin if Text.IsEmpty then Exit; { 在渲染函数 DoRenderLayout 中记录了原来的颜色, 如果当前颜色与渲染时颜色不一致,则重新设置 } if FOldColor <> Color then begin FOldColor := Color; for I := 0 to FFrame.Count - 1 do begin LLine := FFrame[I]; for J := 0 to LLine.Count - 1 do begin LRun := LLine[J]; LRun.SetColor(Color, True); end; end; end; { 检查画布 Canvas 的缩放比例与当前比例是否一致, 如果不一致则重新渲染 } if not SameValue(FScale, ACanvas.Scale, Epsilon) then begin FScale := ACanvas.Scale; FScaleFactor := 1 / FScale; DoRenderLayout; end; { 检查左上顶点是否为0,以此判断是否按照 横向和纵向 进行排列布局 } HorizontalAligned := SameValue(Frac(TopLeft.X), 0.0, TEpsilon.Position) and SameValue(Frac(ACanvas.Matrix.m31), 0.0, TEpsilon.Position); VerticalAligned := SameValue(Frac(TopLeft.Y), 0.0, TEpsilon.Position) and SameValue(Frac(ACanvas.Matrix.m32), 0.0, TEpsilon.Position); { 对输出的帧进行处理,帧(Frame)、行(Line)、渲染单元(Run) 的关系如下 --------GPUFrame------------- |(GPURun)(GPURun)...(GPURun)| <- GPULine (several GPURun's with different font and/or color) |(GPURun) | <- GPULine (no additional styling, so only a single GPURun) |(GPURun) | <- GPULine | | ... | | | | ----------------------------- 帧有若干行,对每一行进行处理 } for I := 0 to FFrame.Count - 1 do begin LLine := FFrame[I]; { 定位每一行的左上顶点位置 } Pos := LLine.TopLeft + TopLeft; { 每行有若干渲染单元,对每个渲染单元进行处理 } for J := 0 to LLine.Count - 1 do begin LRun := LLine[J]; { 得到渲染单元的字体类型 TFontStyle = (fsBold, fsItalic, fsUnderline, fsStrikeOut); TFontStyles = set of TFontStyle; } if LRun.Font <> nil then Styles := LRun.Font.Style else Styles := Self.Font.Style; { 每个渲染单元是按照 字体 + 颜色 进行区分的, 即若干相同字体和颜色的字符,纳入到一个渲染单元中 对每个渲染单元,按照其对应的字体获取 字符渲染处理对象(TCharDic) 每个字符渲染处理对象记录了在当前字体下,该字符对应的 字形、大小,并预先绘制好图片。 其核心思想应该是: 预先将每个字符在不同的字体下,绘制到一个图片中保存, 当需要绘制时,将这些图片直接绘制到画布上,以加快渲染速度 PCharRec = ^TCharRec; TCharRec = record Glyph: TFontGlyph; SrcRect: TRectF; Bitmap: TBitmap; end; TCharDic = class(TDictionary<UCS4Char, PCharRec>) 还有一种做法是,将绘制过程放入TPathData中存储,在绘制时直接调用Path进行快速绘制 } CharDic := GetCharDictionary(LRun.Font); { 画布的调制色彩? } TCustomCanvasGpu(ACanvas).ModulateColor := LRun.Color; { 对渲染单元的每一个字符进行处理 } for K := 0 to LRun.Chars.Count - 1 do begin { 得到该渲染单元对应的预先绘制好的字符图片记录, 如果没有预先绘制,则立即处理 ... UpdateCharRec(...) } Rec := AddOrGetChar(ACanvas, LRun.Chars[K], CharDic, LRun.Font); { 如果图片存在,则处理其绘制的位置 } if Assigned(Rec.Bitmap) then begin { 计算水平位置 } if HorizontalAligned then R.Left := ACanvas.AlignToPixelHorizontally(Pos.X) + Rec.Glyph.Origin.X * FScaleFactor else R.Left := Pos.X + Rec.Glyph.Origin.X * FScaleFactor; { 计算垂直位置 } if VerticalAligned then R.Top := ACanvas.AlignToPixelVertically(Pos.Y) + Rec.Glyph.Origin.Y * FScaleFactor else R.Top := Pos.Y + Rec.Glyph.Origin.Y * FScaleFactor; { 计算宽度 Rec.SrcRect代表原始宽度,再乘以比例 } R.Right := R.Left + (Rec.SrcRect.Width * FScaleFactor); R.Bottom := R.Top + (Rec.SrcRect.Height * FScaleFactor); SrcR := Rec.SrcRect; { 根据是否裁剪进行处理 , 如果裁剪则设置裁剪的范围 } if LRun.IsClipped then begin ClipBounds := LRun.ClipBounds[K]; SrcR.Top := SrcR.Top + ClipBounds.Top * FScale; R.Top := R.Top + ClipBounds.Top; SrcR.Bottom := SrcR.Bottom - ClipBounds.Bottom * FScale; R.Bottom := R.Bottom - ClipBounds.Bottom; SrcR.Left := SrcR.Left + ClipBounds.Left * FScale; R.Left := R.Left + ClipBounds.Left; SrcR.Right := SrcR.Right - ClipBounds.Right * FScale; R.Right := R.Right - ClipBounds.Right; end else { 不裁剪则进行平滑处理 } begin R.Inflate(FSmoothRenderMarginInPixels, FSmoothRenderMarginInPixels); SrcR.Inflate(SmoothRenderMargin, SmoothRenderMargin); end; { 判断是否彩色,并变更画布的调制色彩? } ColoredGlyph := TFontGlyphStyle.ColorGlyph in Rec.Glyph.Style; if ColoredGlyph then TCustomCanvasGpu(ACanvas).ModulateColor := $FFFFFFFF; { 将预先绘制好的内容绘制到画布上 } ACanvas.DrawBitmap(Rec.Bitmap, SrcR, R, Opacity); { 还原画布的调制色彩 } if ColoredGlyph then TCustomCanvasGpu(ACanvas).ModulateColor := LRun.Color; end; { 移动横坐标到本字符的后面,以便进行下一个字符绘制 } Pos.X := Pos.X + (Rec.Glyph.Advance * FScaleFactor); end; { } if LRun.IsTrimmed then begin Rec := AddOrGetChar(ACanvas, FEllipsisChar, GetCharDictionary(Self.Font), Self.Font); TCustomCanvasGpu(ACanvas).ModulateColor := Self.Color; if Assigned(Rec.Bitmap) then begin if HorizontalAligned then R.Left := ACanvas.AlignToPixelHorizontally(Pos.X) + Rec.Glyph.Origin.X * FScaleFactor else R.Left := Pos.X + Rec.Glyph.Origin.X * FScaleFactor; if VerticalAligned then R.Top := ACanvas.AlignToPixelVertically(Pos.Y) + Rec.Glyph.Origin.Y * FScaleFactor else R.Top := Pos.Y + Rec.Glyph.Origin.Y * FScaleFactor; R.Right := R.Left + (Rec.SrcRect.Width * FScaleFactor); R.Bottom := R.Top + (Rec.SrcRect.Height * FScaleFactor); // Draw R.Inflate(FSmoothRenderMarginInPixels, FSmoothRenderMarginInPixels); SrcR.Inflate(SmoothRenderMargin, SmoothRenderMargin); ACanvas.DrawBitmap(Rec.Bitmap, Rec.SrcRect, R, Opacity); end; end; { 处理下划线和删除线 } if ([TFontStyle.fsStrikeOut, TFontStyle.fsUnderline] * Styles) <> [] then begin { 颜色 } FStrokeBrush.Color := LRun.Color; { 线条厚度 } if LRun.Font <> nil then Thickness := LRun.Font.Size / 15 else Thickness := Self.Font.Size / 15; FStrokeBrush.Thickness := Thickness; { 删除线 Pos的位置为最后一个字符的后面 } if TFontStyle.fsStrikeOut in Styles then ACanvas.DrawLine( TPointF.Create(Pos.X - LRun.ImageRect.Width, Pos.Y + LRun.ImageRect.Height / 2), TPointF.Create(Pos.X, Pos.Y + LRun.ImageRect.Height / 2), Opacity,//
不透明 FStrokeBrush); { 下划线 } if TFontStyle.fsUnderline in Styles then ACanvas.DrawLine( TPointF.Create(Pos.X - LRun.ImageRect.Width, Pos.Y + CharDic.Baseline * FScaleFactor + 1.5 * Thickness), TPointF.Create(Pos.X, Pos.Y + CharDic.Baseline * FScaleFactor + 1.5 * Thickness), Opacity, FStrokeBrush); end;//
下一个渲染单元 end;//
下一行 end; TCustomCanvasGpu(ACanvas).ModulateColor := $FFFFFFFF; end;
一个重要的函数数:AddOrGetChar,获取字符处理对象,调用了UpdateCharRec函数。