FireMonkey 源码学习(4)
(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 := to FFrame.Count - do
begin
LLine := FFrame[I];
for J := to LLine.Count - 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 := / 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 := to FFrame.Count - do
begin
LLine := FFrame[I];
{
定位每一行的左上顶点位置
}
Pos := LLine.TopLeft + TopLeft; {
每行有若干渲染单元,对每个渲染单元进行处理
}
for J := to LLine.Count - 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 := to LRun.Chars.Count - 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 /
else
Thickness := Self.Font.Size / ;
FStrokeBrush.Thickness := Thickness;
{
删除线
Pos的位置为最后一个字符的后面
}
if TFontStyle.fsStrikeOut in Styles then
ACanvas.DrawLine(
TPointF.Create(Pos.X - LRun.ImageRect.Width, Pos.Y + LRun.ImageRect.Height / ),
TPointF.Create(Pos.X, Pos.Y + LRun.ImageRect.Height / ),
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函数。
FireMonkey 源码学习(4)的更多相关文章
- FireMonkey 源码学习(5)
(5)UpdateCharRec 该函数的源码分析如下: procedure TTextLayoutNG.UpdateCharRec(const ACanvas: TCanvas; NeedBitma ...
- FireMonkey 源码学习(1)
FireMonkey采用了与VCL不同的机制,很多基础类已经重新编写了,好在一如既往地提供了源代码,故此有机会学习一下. 一.图形引擎 FireMonkey采用了纯图形化技术解决可视化控件,而不是使用 ...
- FireMonkey 源码学习(3)
五.TTextLayoutNG 在FMX.TextLayout.GPU.pas文件中,实现了几个基础功能,其中: (1)渲染单元 在TextLayout中,每一批同字体和颜色的1~n个字符,组成一个最 ...
- FireMonkey 源码学习(2)
三.TControl FireMonkey重写了TControl的代码,实现了众多接口,如下图: 基类上实现了众多功能,这里不详细描述. 四.TEdit 编辑框是从TControl—TStyledCo ...
- FireMonkey 源码学习(6)
(6)GetGlyph和GetBaseline TFontGlyphManager是一个抽象类,在不同平台上的实现是不同的,以Windows为例,在FMX.FontGlyphs.Win.pas文件中定 ...
- Java集合专题总结(1):HashMap 和 HashTable 源码学习和面试总结
2017年的秋招彻底结束了,感觉Java上面的最常见的集合相关的问题就是hash--系列和一些常用并发集合和队列,堆等结合算法一起考察,不完全统计,本人经历:先后百度.唯品会.58同城.新浪微博.趣分 ...
- jQuery源码学习感想
还记得去年(2015)九月份的时候,作为一个大四的学生去参加美团霸面,结果被美团技术总监教育了一番,那次问了我很多jQuery源码的知识点,以前虽然喜欢研究框架,但水平还不足够来研究jQuery源码, ...
- MVC系列——MVC源码学习:打造自己的MVC框架(四:了解神奇的视图引擎)
前言:通过之前的三篇介绍,我们基本上完成了从请求发出到路由匹配.再到控制器的激活,再到Action的执行这些个过程.今天还是趁热打铁,将我们的View也来完善下,也让整个系列相对完整,博主不希望烂尾. ...
- MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)
前言:上篇介绍了下自己的MVC框架前两个版本,经过两天的整理,版本三基本已经完成,今天还是发出来供大家参考和学习.虽然微软的Routing功能已经非常强大,完全没有必要再“重复造轮子”了,但博主还是觉 ...
随机推荐
- python时间和日期
一.time 和 calendar 模块可以用于格式化日期和时间 import time; # 引入time模块 ticks = time.time() print "当前时间戳为:&quo ...
- python基础-abstractmethod、__属性、property、setter、deleter、classmethod、staticmethod
python基础-abstractmethod.__属性.property.setter.deleter.classmethod.staticmethod
- WebStorage (1) 实例
实例代码 <p>本页面每5秒刷新一下,这是您第<mark></mark>次进入本页面!</p> <script> if (window.lo ...
- jQuery属性--attr(name|properties|key,value|fn)和removeAttr(name)
attr(name|properties|key,value|fn) 概述 设置或返回被选元素的属性值 参数 key,function(index, attr) 1:属性名称:2:返回 ...
- Nginx技术研究系列1-通过应用场景看Nginx的反向代理
随着我们业务规模的不断增长,整个系统规模由两年前的几十台服务器,井喷到现在2个数据中心,接近400台服务器,上百个WebApi站点,上百个域名. 这么多的WebApi站点这么多的域名,管理和维护成本很 ...
- Python大神成长之路: 第三次学习记录 集合 函数 装饰 re
学习记录day03 字符串可以直接切片,But字符串不可修改 字符串修改:生成了一个新的字符串 LIst修改,在原基础上修改(原内存上) 集合是一个无序的,不重复的数据组合,它的主要作用如 ...
- mybatis源码解析2---SqlSessionFactoryBuilder、SqlSessionFactory解析
1.先看一个mybatis最简单的Demo String resource = "mybatis-config.xml"; //1.流形式读取mybatis配置文件 InputSt ...
- 多进程wait、僵尸进程、孤儿进程、prctl
1.概念 1.孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程.孤儿进程将被init进程(进程号为1)所收养,从而保证每个进程都会有一个父进程.而Init进程会自 ...
- es6基本语法
//let和const申明变量和常量 //作用域只限于当前代码块 //使用let申明的变量作用域不会提升 //在相同的作用域下不能申明相同的变量 //for循环体现let的父子作用域 二.es6的解构 ...
- Matlab基础部分1