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 := 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函数。

FireMonkey 源码学习(4),布布扣,bubuko.com

时间: 2024-10-20 15:17:36

FireMonkey 源码学习(4)的相关文章

FireMonkey 源码学习(5)

(5)UpdateCharRec 该函数的源码分析如下: procedure TTextLayoutNG.UpdateCharRec(const ACanvas: TCanvas; NeedBitmap: Boolean; var NewRec: PCharRec; HasItem: Boolean; const CharDic: TCharDic; const AFont: TFont; const Ch: UCS4Char; const NeedPath: Boolean = False);

FireMonkey 源码学习(3)

五.TTextLayoutNG 在FMX.TextLayout.GPU.pas文件中,实现了几个基础功能,其中: (1)渲染单元 在TextLayout中,每一批同字体和颜色的1~n个字符,组成一个最基本的渲染单元TGPURun,1~n个渲染单元构成一行(TGPULine),1~n行构成一帧(Frame).如下图: 行和帧只是一个组织方式,其中没有太多的内容.主要的实现还是在渲染单元(TGPURun)中. (2)TCharDic和TFamilyDic TextLayout的实现思路是: 每个渲染

FireMonkey 源码学习(2)

三.TControl FireMonkey重写了TControl的代码,实现了众多接口,如下图: 基类上实现了众多功能,这里不详细描述.   四.TEdit 编辑框是从TControl-TStyledControl继承下来的,并实现了众多接口,如下图: 从实现的接口可以看到,包括键盘.光标.文字处理等都进行了逐一处理,从中可以看到每种功能的实现思路和方式. 我最关心的是文字的输出处理,FireMonkey使用了一个独立的类TTextLayout来进行文字输出管理:   这是一个抽象类,具体的实现

FireMonkey 源码学习(1)

FireMonkey采用了与VCL不同的机制,很多基础类已经重新编写了,好在一如既往地提供了源代码,故此有机会学习一下. 一.图形引擎 FireMonkey采用了纯图形化技术解决可视化控件,而不是使用Windows控件作为基础.FireMonkey所使用的图形处理技术如下: Mac   HD 使用 Quartz   3D 使用 OpenGL Windows   HD 使用 Direct2D,如果无法获得 Direct2D, FireMonkey 使用 GDI+ 用于 HD   3D 使用 Dir

jquery源码学习

jQuery 源码学习是对js的能力提升很有帮助的一个方法,废话不说,我们来开始学习啦 我们学习的源码是jquery-2.0.3已经不支持IE6,7,8了,因为可以少学很多hack和兼容的方法. jquery-2.0.3的代码结构如下 首先最外层为一个闭包, 代码执行的最后一句为window.$ = window.jquery = jquery 让闭包中的变量暴露倒全局中. 传参传入window是为了便于压缩 传入undefined是为了undifined被修改,他是window的属性,可以被修

Hadoop源码学习笔记(1) ——第二季开始——找到Main函数及读一读Configure类

Hadoop源码学习笔记(1) ——找到Main函数及读一读Configure类 前面在第一季中,我们简单地研究了下Hadoop是什么,怎么用.在这开源的大牛作品的诱惑下,接下来我们要研究一下它是如何实现的. 提前申明,本人是一直搞.net的,对java略为生疏,所以在学习该作品时,会时不时插入对java的学习,到时也会摆一些上来,包括一下设计模式之类的.欢迎高手指正. 整个学习过程,我们主要通过eclipse来学习,之前已经讲过如何在eclipse中搭建调试环境,这里就不多述了. 在之前源码初

HSQLDB源码学习——数据库安装启动及JDBC连接

HSQLDB 是一个轻量级的纯Java开发的开放源代码的关系数据库系统.因为HSQLDB的轻量(占用空间小),使用简单,支持内存运行方式等特点,HSQLDB被广泛用于开发环境和某些中小型系统中. 在http://sourceforge.net/projects/hsqldb/files/下载了HSQLDB 1.8.0版本.把下载的zip文件解压缩至任意目录例如c:\hsqldb1.8便完成安装. hsqldb有四种运行模式: 一.内存(Memory-Only)模式:所有数据都在内存里操作.应用程

lodash源码学习(10)

_.delay(func, wait, [args]) 延迟wait毫秒之后调用该函数,添加的参数为函数调用时的参数 //delay.js var baseDelay = require('./_baseDelay'),//baseDelay方法 baseRest = require('./_baseRest'),//创建使用rest参数方法 toNumber = require('./toNumber');//转化为数字 /** * * @param {Function} func 需要延迟执

lodash源码学习(2)

继续学习lodash,依然是数组的方法 “Array” Methods _.indexOf(array, value, [fromIndex=0]) 获取value在数组 array所在的索引值 使用 SameValueZero方式比较(第一个全等===的元素). 如果 fromIndex 值是负数, 则从array末尾起算 该方法依赖于strictIndexOf和baseIndexOf方法,先看它们的源码 //_strictIndexOf.js /** * _.indexOf的专业版本,对元素