Apple - Text Layout Programming Guide

本文翻译整理自:Text Layout Programming Guide(更新日期:2014-02-11
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/TextLayout/TextLayout.html#//apple_ref/doc/uid/10000158i


文章目录

  • 一、文本布局编程指南简介
    • 1、谁应该阅读此文档
    • 2、本文件的组织
    • 3、另见
  • 二、布局管理器
    • 1、线程安全
    • 2、布局过程
    • 3、字形绘图
  • 三、Typesetters
    • 1、填充线片段矩形
    • 2、排字机行为和版本
    • 3、NSTypesetter的设计
  • 四、线段生成
  • 五、沿任意路径布置文本
  • 六、绘制字符串
    • 1、使用字符串绘制便利方法
    • 2、使用NSCell绘制文本
    • 3、使用NSLayoutManager绘制文本
    • 4、字符串绘制和排字机行为
  • 七、计算文本高度
  • 八、计算文本行数
  • 九、使用文本表
    • 1、添加文本表面板
    • 2、以编程方式支持文本表
    • 3、文本表模型
    • 4、控制文本块外观
    • 5、表格布局过程


一、文本布局编程指南简介

文本布局编程指南描述了Cocoa文本系统如何布局文本。
文本布局是将一串文本字符、字体信息和页面规范转换为放置在页面特定位置的字形行的过程,适合显示和打印。


1、谁应该阅读此文档

如果您需要了解文本系统布局机制是如何工作的,以及如何直接使用NSLayoutManager对象来实现文章中描述的编程目标,您应该阅读本文档。

要理解本文档中的信息,您应该阅读 Cocoa文本体系结构指南
您还应该了解基本的Cocoa编程约定,例如委托。


2、本文件的组织

本编程主题包含以下文章:

  • 布局管理器介绍了NSLayoutManager类,描述了它的特性并解释了它如何执行文本布局。
  • Typesetters描述了typesetter对象的职责,该对象从NSTypesetter的具体子类实例化,该子类代表布局管理器生成行片段和字形位置。
  • 行片段生成解释了排字器和文本容器如何协同工作以创建行片段矩形。
  • Drawing Strings解释了如何使用布局管理器而不是NSString便捷方法来高效地绘制文本字符串。
  • 沿任意路径布局文本展示了如何在没有文本视图的情况下使用布局管理器沿计算路径布局字形。
  • 计算文本高度显示了如何确定在固定宽度区域中布局的文本块的高度。
  • Counting Lines of Text解释了如何以编程方式计算文本字符串中的行数,无论这些行是由硬换行符定义还是在文本容器中布局。
  • 使用文本表说明了如何在OS X 10.4及更高版本中为应用程序添加文本表支持。

3、另见

如需进一步阅读,请参阅以下文件:

  • 文本系统存储层概述 讨论了Cocoa文本系统用来存储用于文本布局的文本和几何图形信息的工具。
  • 文本属性编程主题 描述了Cocoa文本系统维护的文本相关属性,这些属性为段落和文档提供富文本和其他格式信息的区别特征。

二、布局管理器

布局管理器类NSLayoutManager为Cocoa文本系统中的文本显示提供了中央控制对象。

一个NSLayoutManager对象执行以下操作:

  • 控制文本存储和文本容器对象
  • 从字符生成字形
  • 计算字形位置并存储信息
  • 管理字形和字符的范围
  • 在视图请求时在文本视图中绘制字形
  • 管理用于段落样式控制的标尺
  • 计算文本行的边框矩形
  • 控制断字
  • 操作字符和字形属性

在model-view-controller范式中,NSLayoutManager是控制器。
NSTextStorageNSMutableAttributedString的子类,它提供了模型的一部分,保存了一串文本字符,这些字符具有字体、样式、颜色和大小等属性。
NSTextContainer也可以被认为是模型的一部分,因为它对文本布局所在页面的几何布局进行建模。
NSTextView(或另一个NSView对象)提供了显示文本的视图。
NSLayoutManager充当文本系统的控制器,因为它指示字形生成器将文本存储对象中的字符转换为字形,指示排字器根据一个或多个文本容器对象的尺寸将它们排成行,并协调一个或多个文本视图对象中的文本显示。
图1说明由布局管理器协调的文本显示的组成。


图1 文本显示的组成

在这里插入图片描述


您可以将文本系统配置为具有多个布局管理器,如果您需要以多种方式布局同一个NSTextStorage对象中的文本。
例如,您可能希望文本在一个视图中显示为连续的厨房,在另一个视图中分割成页面。
有关文本对象不同排列的更多信息,请参阅 通用配置。


1、线程安全

一般来说,一个给定的布局管理器(和相关的对象)不应该一次在多个线程上使用。
大多数布局管理器用于主线程,因为它是显示其文本视图的主线程,并且因为后台布局发生在主线程上。
但是,只要对象图包含在单个线程中,您就可以使用NSLayoutManager在辅助线程上布局和渲染文本。

如果必须在辅助线程上使用布局管理器,则应用程序有责任确保不会从其他线程同时访问对象。
首先,通过禁用后台布局和自动显示,确保在辅助线程上使用布局管理器时不显示与该布局管理器关联的NSTextView对象(如果有)。
例如,您可以发送文本视图lockFocusIfCanDraw以阻止主线程显示(完成后发送unlockFocus)。
其次,通过发送setBackgroundLayoutEnabled:withNO来关闭该布局管理器在辅助线程上使用时的后台布局。


2、布局过程

布局管理器在两个单独的步骤中执行文本布局:字形生成和字形布局。
布局管理器懒惰地执行这两个布局步骤,即按需执行。
因此,一些NSLayoutManager方法会导致字形生成,而其他方法不会,字形布局也是如此。
在生成字形并计算其布局位置后,布局管理器会缓存信息以提高后续调用的性能。

布局管理器缓存字形、属性和布局信息。
它跟踪因更改文本存储中的字符而失效的字形范围。
有两种方法可以自动使字符范围失效:如果需要生成字形或需要布局字形。
如果您愿意,您可以手动使字形或布局信息无效。
当布局管理器收到要求了解无效范围内的字形或布局的消息时,它会根据需要生成字形或重新计算布局。

NSLayoutManager使用NSTypesetter对象来执行实际的字形布局。
有关详细信息,请参阅Typesetters。
图2说明了布局过程中涉及的对象的交互。


图2 文本布局过程

在这里插入图片描述


以下步骤(编号与图2中的数字相关联)解释了布局管理器如何控制文本布局:

  1. 文本存储中的文本发生更改,使字形或其布局位置或两者无效。
    例如,由于用户在文本视图中编辑文本,文本视图导致文本存储内容的更改。
    或者另一个对象可以以编程方式更改文本。
  2. 文本存储通过发送消息来通知其关联的布局管理器(或多个管理器)无效的字符范围textStorage:edited:range:changeInLength:invalidatedRange:该消息指定更改是否影响字符、属性或两者;更改的字符范围;以及属性修复后影响的范围。
  3. 布局管理器更新其内部数据结构以反映无效范围。
    属性更改可能会也可能不会影响字形生成和布局。
    例如,更改文本的颜色不会影响它的布局方式。
  4. 为了通知其关联的文本视图需要重新显示无效区域,布局管理器发送消息setNeedsDisplayInRect:avoidAdditionalLayout:
    此时可能会发生以下任何事情。
    如果文本视图的无效部分可见,文本视图会向布局管理器询问任何需要的字形及其位置。
    如果无效区域当前不可见,视图不会立即调用布局。
    但是,当应用程序没有要处理的事件时,可能会发生后台(空闲时间)布局。
    默认情况下,后台布局是打开的,尽管您可以为任何单独的布局管理器关闭它。
  5. 当文本视图向布局管理器询问字形和位置时,布局过程开始。
    (其他消息也可以调用布局。
    布局管理器头文件和引用留档指定哪些方法导致字形生成和布局发生。)
  6. 布局管理器从新编辑的字符范围生成字形流并缓存字形。
    字形生成是将特定字体中的字符快速首次转换为字形。
    (没有字体信息,就无法生成字形。)
    布局过程的后期阶段可以对字形流进行更改。
  7. 生成所需字形后,布局管理器调用其排字器将字形布局成一个或多个行片段,向排字器发送layoutGlyphsInLayoutManager:startingAtGlyphIndex:maxNumberOfLineFragments:nextGlyphIndex:消息。
    在此过程中,排字器可以执行字形替换;例如,它可以替换一个连字符字形来代替两个或多个单字符字形。
  8. 排字器生成与文本容器通信的行片段矩形,并确定每个字形的位置,如排字器和行片段生成中所述。
  9. 排字器将带有字形和位置的行片段矩形发送到布局管理器,布局管理器将其内部数据结构中的信息提交为有效布局。

3、字形绘图

除了生成字形和执行布局之外,布局管理器还会绘制文本视图中的字形。
当文本视图要求布局管理器找出给定视图矩形中的字形并显示它们时,就会发生绘图。
布局管理器有绘制字形及其背景的方法。
这些方法通过调用Quartz图形层来完成所有必要的绘图。
它们绘制背景,设置字体和颜色,绘制字形、下划线和任何临时属性。

大多数NSLayoutManager方法使用容器坐标,而不是视图坐标。
文本系统期望视图坐标被翻转,就像NSTextView一样。
如果您有一个视图坐标需要转换为容器坐标,减去文本视图的textContainerOrigin值以获得容器坐标。
字形位置相对于它们的线片段边界矩形的原点表示。
图3显示了这些坐标系之间的关系。


图3 视图、容器和线段坐标

在这里插入图片描述


属性是布局管理器在排版和布局过程中应用于字符的品质,例如字体、大小和颜色。
文本存储在与字符串一起存储的字典中保留了许多属性,但其他属性是临时的,仅由布局管理器在布局过程中维护。
临时属性取代与字体或段落相关的属性。
绘图方法还处理与文本视图相关的其他属性——例如,不同的背景颜色——当在文本视图中选择正在绘制的字形时。
绘图方法调用一些其他公共NSLayoutManager方法,例如drawUnderlineForGlyphRange:underlineType:baselineOffset:lineFragmentRect:lineFragmentGlyphRange:containerOrigin:,如果你想做不同的事情,你可以覆盖它。
请参阅 文本属性编程主题 了解更多信息。

布局管理器还处理附件的表示在字形绘制期间。
文本系统将附件存储为特殊字符的属性。
典型的附件是文件,但也可以是内存中的数据。
文件附件通常通过绘制图标来处理。
然而,如果您实现不同的行为,附件可以做的远不止这些。
在布局期间,附件单元格(NSTextAttachmentCell)告诉布局管理器它的大小,因此它可以像字形一样布局。
相应地,文本的行高和字符位置被调整以适应附件单元格。
在绘制期间,布局管理器要求附件单元格自行绘制。
有关详细信息,请参阅 文本附件编程主题

布局管理器保留和重用尽可能多的布局信息,以最小化重新计算字形位置。
例如,如果已经为需要布局的无效字符范围生成了字形,布局管理器会尝试优化布局过程。
在最好的情况下,布局中的这些漏洞可以通过移动文本容器中的行片段位置来填补。

NSLayoutManager提供了一个公共API,用于获取字符中的字形。
但是,这个过程很复杂:您不能简单地将单个字符转换为字形,因为字符和字形之间的关系是多对多的。
也就是说,文本存储中的一个字符可以映射到多个字形,反之亦然。
因此,您可以使用NSLayoutManager方法glyphRangeForTextContainer:获取文本容器中所有字符的字形,或者glyphRangeForCharacterRange:actualCharacterRange:获取一系列字符的字形。


三、Typesetters

布局管理器使用称为排版器的辅助对象来布局行片段中的字形。
排版器对象是从NSTypesetter的具体子类实例化的。

与Cocoa文本系统中的其他对象一起工作,排字器创建行片段矩形,将字形放置在行片段中,通过换行和断字确定换行符,并处理制表符定位。
排字器还确定行间距、段落行间距和双向字形的从右到左定位。


1、填充线片段矩形

排字器对象生成行片段通过与文本容器通信,如行片段生成中所述。
排字器确定合适的行片段大小和位置,并以容器坐标返回。

创建行片段矩形后,排字人员根据layoutGlyphsInLayoutManager:startingAtGlyphIndex:maxNumberOfLineFragments:nextGlyphIndex:来自布局管理器的消息确定字形在其中的位置。
排字人员报告相对于其行片段边界矩形原点的字形位置。
排字人员填充行片段,直到它超出行片段的宽度。
然后它通过包装文本或连字符连接最后一个单词来创建换行符。
在此步骤中,排字人员执行字形替换,如果需要,并且可以添加字形到字形流中。
例如,排字人员可以将连字符字形替换为一个或多个单字符字形,或者可以在字形流中添加连字符。

NSTypesetter子类可以通过覆盖shouldBreakLineByWordBeforeCharacterAtIndex:方法来控制单词边界的换行。
同样,子类可以通过覆盖shouldBreakLineByHyphenatingBeforeCharacterAtIndex:方法来干预断字。

每当布局行的宽度除以行矩形的宽度超过断字阈值由布局管理器维护,排字器调用一个内部断字器对象,该对象试图在行中的最后一个单词中找到断字点。
如果断字器找到一个好的点,排字器会在行片段矩形的末尾插入一个连字符号。

断字由称为断字因子的阈值控制,该阈值由布局管理器维护。
您可以使用NSLayoutManager方法setHyphenationFactor:.断字因子是一个介于0.0和1.0之间的浮点数。
默认情况下,它的值为0.0,这意味着断字是关闭的。
将断字因子设置为1.0会导致排字程序总是尝试断字。


2、排字机行为和版本

文本系统使用一个共享的、可重入的排字器实例,该实例由NSLayoutManager方法typesetter提供。
NSLayoutManager方法setTypesetterBehavior:在OS X 10.2版之前附带的原始默认排字器中选择一个封装Apple Type Services(ATS)的排字器OS X 10.2版附带的、OS X 10.3版附带的基于ATS的排字器的增强版本,以及OS X 10.4版中引入的排字器行为。
NSTypesetterBehavior枚举定义了相关常量。

实现原始排字行为的NSTypesetter子类NSSimpleHorizontalTypesetter,在NSTypesetter.h头文件中定义。
NSSimpleHorizontalTypesetter仅支持从左到右扫描和向下移动的字形布局。
NSSimpleHorizontalTypesetter在OS X 10.4及更高版本中已弃用。

OS X版本10.2中引入的排字器行为由NSATSTypesetter类实现,该类定义在NSATSTypesetter.h头文件中。
NSATSTypesetter提供了增强的行间距和字符行间距精度,并支持更多的语言,包括双向语言,比原来的NSSimpleHorizontalTypesetter

OS X版本10.3引入了NSATSTypesetter的新版本,它声明了NSATSTypesetterNSGlyphGenerator的公共API。
这些API打开了排字器,以便与具有不同于传统Cocoa文本系统设计的自定义布局引擎一起使用,如NSTypesetter的设计中所述。
在OS X版本10.4中,这些API转移到NSTypesetter

除非您需要早期排字器版本的特定行为,否则您应该使用最新版本的NSATSTypesetter或对其进行子类化。

在测量和呈现文本时使用相同的排版行为非常重要,以避免段落行间距、行距和头部缩进处理的差异。
有关排版行为不匹配的更多信息,请参阅字符串绘制和排版行为。


3、NSTypesetter的设计

在Cocoa文本系统中,布局管理器拥有排字器和字形生成器作为私有对象,并维护文本容器数组,如布局管理器中所述。
排字器概念与布局管理器和文本容器概念紧密耦合。
排字器的职责是用字形生成器提供的字形填充数组中的文本容器。
默认情况下,NSATSTypesetter以这种方式工作。
然而,NSTypesetter旨在使开发人员能够将其与Cocoa文本系统的其他组件分离。

NSTypesetter的设计将原始的、核心的排字器与Cocoa文本系统的其余部分隔离开来,如图1所示。
NSTypesetter具有核心排版引擎、布局阶段接口和字形存储接口层,与文本系统通信并驱动布局引擎。
核心排版引擎通过简化的API提供高级排版功能。
核心排版引擎在无限的水平线中布置字形,对文本容器或文本方向一无所知。
字形存储接口层调用文本系统生成行片段矩形,并确保它们正确地适合页面。


图1 NSTypesetter的设计

在这里插入图片描述


针对NSTypesetter的API设计有两个主要目标。
首先是打破两个类和NSLayoutManager之间的联系,允许开发人员在不使用NSLayoutManager的情况下深入挖掘Cocoa的排版功能。
第二个目标是提供覆盖点,允许开发人员扩展排版过程的各个方面。
此外,对这些类的直接访问使得将带有自己布局引擎的Carbon、Windows或UNIX应用程序移植到Cocoa变得更加容易。

NSTypesetter将其方法分类如下:

  • 字形存储接口(NSGlyphStorageInterface)声明了与字形存储设施(Cocoa中的NSLayoutManager)接口的所有原始方法。
    通过覆盖所有这些方法,应用程序可以实现一个与自定义字形存储设施和布局管理器交互的NSTypesetter子类。
    这些方法的默认实现调用NSLayoutManager
    NSTypesetter从布局管理器复制当前正在处理的行片段的字形,并对副本执行布局、替换、插入和删除。
    作为布局过程的最后一步,它将生成的字形移动到字形存储中。
    大多数NSGlyphStorageInterface方法包括insertGlyph:atGlyphIndex:characterIndex:用于将结果复制回字形存储的最后一步。
    由于字形索引和字符索引在布局过程中是标称的,因此您应该等到最后一个过程再修改NSLayoutManager
  • 布局阶段接口(NSLayoutPhaseInterface)声明在文本布局期间调用的控制点(如果实现)。
    这些方法调用充当布局过程中发生的事件的通知。
    如果需要,NSTypesetter子类可以覆盖这些方法中的任何一个,以修改布局过程的各个方面。
    例如,排字器调用willSetLineFragmentRect:forGlyphRange:usedRect:baselineOffset:在它调用之前setLineFragmentRect:forGlyphRange:usedRect:baselineOffset:将实际的行片段矩形位置存储在布局管理器中。
  • 其余的NSTypesetter方法是原始的排字器方法,自定义布局管理器可以调用这些方法来直接控制排字器。

通过其分层设计,NSTypesetter可以实例化并在Cocoa文本系统的标准配置中使用,或者子类化并适应于与另一个文本系统一起工作,即使是一个对如何执行页面布局有完全不同概念的系统。


四、线段生成

一个NSTypesetter对象在NSTextContainer对象中以字形行的形式放置文本。
这些行在NSTextContainer对象中的布局由其形状决定。
例如,如果文本容器的某些部分比其他部分窄,则这些部分中的行必须缩短;如果区域中有孔,则某些行必须被分割;如果整个区域都有间隙,则必须移动与之重叠的行以进行补偿。

文本系统当前提供的内置排字机仅支持水平文本布局。
然而,文本系统可以支持排字机沿水平或垂直以及任何方向排列文本。
这种类型的移动称为扫描方向,由Objective-C中的NSLineSweepDirection类型和Java中的扫描方向常量表示。
线条然后前进的方向称为线条移动方向,并由Objective-C中的NSLineMovementDirection类型和线条移动Java常量表示。
每个都以不同的方式影响线段矩形的调整:矩形可以沿扫描方向移动或缩短,并在线移动方向上移动(但不能调整大小)。

排字器对象为给定的行提出一个矩形,然后要求NSTextContainer对象调整矩形以适应。
提出的矩形通常跨越文本容器的边界矩形,但它可以更窄或更宽,也可以部分或完全位于边界矩形之外。
排字器发送文本容器以调整提出的矩形的消息是lineFragmentRectForProposedRect:sweepDirection:movementDirection:remainingRect:,它返回可用于提出的矩形的最大矩形,基于文本布局的方向。
它还返回一个包含任何剩余空间的矩形,例如留在文本容器中的孔或间隙的另一侧。
图1中说明了这个过程。


图1 不规则文本容器中的行片段拟合

在这里插入图片描述


对于图1中的三个示例,扫描方向是NSLineSweepRight,线移动方向是NSLineMovesDown
在第一个示例中,建议的矩形跨越区域的边界矩形,并被文本容器缩短以适合沙漏形状,没有余数。

在第二个例子中,提议的矩形穿过一个孔,因此文本容器必须返回一个较短的矩形(左边的白色矩形)和一个余数(右边的白色矩形)。
排字人员提议的下一个矩形将是这个余数矩形,文本容器将原封不动地返回它。

在第三个例子中,一个间隙穿过整个文本容器。
这里,文本容器将建议的矩形向下移动,直到它完全位于容器的区域内。
如果这里的行移动方向是NSLineDoesntMove,文本容器将必须返回NSRect.ZeroRect,表明该行根本不合适。
在这种情况下,排字人员可以提出不同的矩形或移动到不同的容器。
当文本容器移动行片段矩形时,布局管理器会将此考虑到后续行。

排字器在实际将文本放入矩形时进行最后一次调整。
这种调整是由NSTextContainer对象固定的少量调整,称为行片段填充,它定义了行片段矩形留空的每一端的部分。
文本在行片段矩形中插入这个量(矩形本身不受影响)。
填充允许对文本容器边缘和任何孔周围的区域进行小规模调整,并防止文本直接与该区域附近显示的任何其他图形相邻。
您可以使用setLineFragmentPadding:方法更改填充的默认值。
请注意,行片段填充不是表示边距的合适方法;您应该为文档边距设置NSTextView对象的位置和大小,或者为文本边距设置段落边距属性。

除了行片段矩形本身之外,排字器还返回一个名为已使用矩形的矩形。
这是行片段矩形中实际包含要绘制的字形或其他标记的部分。
按照惯例,这两个矩形都包括行片段填充和根据字体的行高度量和段落的行距参数计算的行间空间。
但是,段落行间距(前后)和文本周围添加的任何空间,例如由中心间距文本引起的空间,仅包含在行片段矩形中,而不包含在已使用的矩形中。

有关文本容器的更多信息,请参见布局几何:NSTextContainer类。
有关布局过程的更多信息,请参见布局管理器。


五、沿任意路径布置文本

Cocoa文本系统通常在文本视图中以水平线排列文本。
然而,也可以只使用存储字符和生成字形所需的文本对象,同时手动计算最终字形位置并自己绘制字形。

要沿着任意路径布局文本,您需要使用三个基本的非视图文本对象:NSTextStorage保存文本,NSTextContainer建模文本布局区域,NSLayoutManager生成字形和布局信息。
最后,您在自定义NSView对象中绘制字形。

首先,创建并初始化文本存储、文本容器和布局管理器的实例。
使用要布局的文本字符串初始化文本容器。
然后将这些对象连接在一起:文本存储对象保留对布局管理器的引用,布局管理器保留对文本容器的引用。
例1可以驻留在显示文本的自定义NSView对象的初始化方法中,它说明了这个过程。


例1 创建和配置非视图文本对象

NSTextStorage *textStorage;
NSLayoutManager *layoutManager;
NSTextContainer *textContainer;
 
textStorage = [[NSTextStorage alloc] initWithString:@"This is the string of text in the text storage."];
layoutManager = [[NSLayoutManager alloc] init];
textContainer = [[NSTextContainer alloc] init];
[layoutManager addTextContainer:textContainer];
[textContainer release];
[textStorage addLayoutManager:layoutManager];
[layoutManager release];

您“添加”这些引用而不是“设置”它们的原因是因为布局管理器可以有多个文本容器,而一个文本存储对象可以有多个布局管理器。
还要注意内存管理此过程的含义:因为布局管理器保留文本容器,而文本存储保留布局管理器,所以您可以在连接对象时立即释放它们。
但是,您应该在dealloc方法中显式释放文本存储对象。

告诉布局管理器不要使用屏幕字体,因为它们不能正确缩放或旋转(默认情况下允许使用屏幕字体):

[layoutManager setUsesScreenFonts:NO];

接下来,强制布局管理器为文本存储对象中的字符生成字形,并让它计算字形在一个简单的矩形容器中布局的位置。
然后转换位置并调用布局管理器来绘制字形。
这可以在视图的drawRect:方法中完成。

以下消息强制布局并返回文本存储对象中字符串的字形:

NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; 

例2中的代码完成了实际的绘图。


例2 绘制字符串

NSGraphicsContext *context = [NSGraphicsContext currentContext];
NSAffineTransform *transform = [NSAffineTransform transform];
[transform rotateByDegrees:30.0];
[context saveGraphicsState];
[transform concat];
[self lockFocus];
[layoutManager drawGlyphsForGlyphRange:glyphRange
                                atPoint:NSMakePoint(50.0, 50.0)];
[self unlockFocus];
[context restoreGraphicsState];

这个片段只是在要求布局管理器绘制字形之前将图形上下文逆时针旋转30度,但是可以使用另一种算法来计算更复杂的布局路径。
这个讨论简化了这项技术,以便专注于与布局管理器的交互。

这个 CircleView 示例提供了一个应用程序的源代码,说明了该技术,但以更健壮的方式完成。
CircleView计算一个位置并单独绘制每个字形。


六、绘制字符串

在Cocoa中以编程方式绘制文本的方法有三种:使用NSStringNSAttributedString的方法,使用NSCell的方法,以及直接使用NSLayoutManager
NSLayoutManager是最有效的。


1、使用字符串绘制便利方法

NSString类有两个方便的方法用于直接在NSView对象中绘制字符串对象:drawAtPoint:withAttributes:drawInRect:withAttributes:
对于具有与范围和单个字符相关联的多个属性的字符串,必须使用NSAttributedString
您可以使用drawAtPoint:drawInRect:方法绘制字符串(在焦点集中的NSView中)。
这些方法设计用于绘制少量文本或必须很少绘制的文本。
每次调用它们时,它们都会创建和处理各种支持文本对象,包括NSLayoutManager

然而,对于文本的重复绘制,字符串绘制便利方法效率不高,因为它们在幕后做了很多工作。
例如,要绘制Unicode文本,您必须首先将字符转换为字形,即字体的元素。
字形生成很复杂,因为几个字符可能会产生一个字形,反之亦然,这取决于上下文和其他因素。
此外,系统为字形转换做了大量设置工作,字符串绘制便利方法每次绘制字符串时都会做这项工作。
使用布局管理器直接提供了显着的性能改进,因为它缓存了字形布局和大小信息。


2、使用NSCell绘制文本

此外,NSCell类还提供了用于显示和编辑文本的原语。
NSBrowserNSTableView使用NSCell文本绘制方法。
NSCell的文本绘制比使用字符串方便方法更有效,因为它缓存了一些信息,例如文本矩形的大小。
因此,对于重复显示相同的文本,NSCell效果很好,但要最有效地显示任意文本字符串,请直接使用NSLayoutManager


3、使用NSLayoutManager绘制文本

如果您使用NSTextView类来显示文本,或者从Interface Builder数据面板拖动文本视图对象,或者使用NSTextView方法initWithFrame:方法以编程方式创建文本视图,Cocoa会自动创建一个NSLayoutManager实例来绘制文本。
但是,如果您使用NSTextView initWithFrame:textContainer:方法创建文本视图,或者如果您需要将文本直接绘制到不同类型的NSView对象中,则必须显式创建NSLayoutManager

要使用NSLayoutManager将文本字符串直接绘制到视图中,您必须创建并初始化文本系统的三个基本非视图组件。
首先创建一个NSTextStorage对象来保存字符串。
然后创建一个NSTextContainer对象来描述文本的几何区域。
然后创建NSLayoutManager对象,并通过将布局管理器添加到文本存储对象并将文本容器添加到布局管理器来将三个对象挂钩在一起。
例1中的代码可以驻留在视图的initWithFrame:方法中,说明了这个过程。


例1 创建和配置非视图文本对象

NSTextStorage *textStorage = [[NSTextStorage alloc]
    initWithString:@"This is the text string."];
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
NSTextContainer *textContainer = [[NSTextContainer alloc] init];
[layoutManager addTextContainer:textContainer];
[textContainer release];
[textStorage addLayoutManager:layoutManager];
[layoutManager release];

您可以释放文本容器,因为布局管理器保留了它,您可以释放布局管理器,因为文本存储对象保留了它。

要想直接在视图中绘制字形,可以在视图的drawRect:方法中使用NSLayoutManager方法drawGlyphsForGlyphRange:
但是,必须先将要绘制的字符范围转换为字形范围。
如果需要在文本存储对象中选择文本的子范围,可以使用glyphRangeForCharacterRange:actualCharacterRange:方法。
如果要在文本存储对象中绘制整个字符串,可以使用glyphRangeForTextContainer:方法,如例2所示(它使用例1中的layoutManager变量名)。


例2 直接在视图中绘制字形

NSRange glyphRange = [layoutManager
    glyphRangeForTextContainer:textContainer];
[self lockFocus];
[layoutManager drawGlyphsForGlyphRange: glyphRange atPoint: rect.origin];
[self unlockFocus];

4、字符串绘制和排字机行为

Cocoa在排字行为方面绘制文本的三种方法之间存在差异,排字行为和版本中有描述。
默认情况下,应用程序套件提供的字符串绘制便利方法和NSCell对象使用NSTypesetterBehavior_10_2_WithCompatibility,而NSLayoutManager对象使用NSTypesetterLatestBehavior
在测量和渲染文本时使用相同的排字行为非常重要,以避免段落行间距、行距和头部缩进处理的差异。

如果您必须以一种方式测量文本并以另一种方式呈现文本,请使用NSLayoutManager和NSTypesetter定义的setTypesetterBehavior:方法NSTypesetter以匹配。
例如,如果您需要使用NSLayoutManager对象来测量文本并使用便利字符串绘制方法来绘制它,请将布局管理器的排字程序行为更改为NSTypesetterBehavior_10_2_WithCompatibility


七、计算文本高度

有时您可能需要知道文本字符串在固定宽度区域中布局后形成的文本块的高度。
NSLayoutManager类可以非常简单地做到这一点。
本文说明了在单个函数中实现的技术。

注意 : 您不需要使用此技术来查找单行文本的高度。
NSLayoutManager方法defaultLineHeightForFont:返回该值。
默认行高是字体最高升序的总和,加上其最深降序的绝对值,加上其前导。

计算文本高度的基本技术使用文本系统的三个基本非视图组件:NSTextStorageNSTextContainerNSLayoutManager
文本存储对象保存要测量的字符串;文本容器指定布局区域的宽度;布局管理器进行布局并返回高度。

要为计算设置文本系统,您需要测量文本字符串、字符串的字体和文本容器建模的区域的宽度。
您可以将这些值传递给具有如下声明的函数:

float heightForStringDrawing(NSString *myString, NSFont *myFont,
    float myWidth);

函数声明中的参数名称在定义方法体的以下代码片段中显示为变量。

首先,您实例化所需的文本对象并将它们挂钩在一起。
您为文本存储对象使用指定的初始化器,它将字符串指针作为参数。
同样,文本容器的指定初始化器将容器大小作为其参数。
您将容器宽度设置为所需的宽度,并将高度设置为任意大的值,如以下代码片段所示:

NSTextStorage *textStorage = [[[NSTextStorage alloc]
        initWithString:myString] autorelease];
NSTextContainer *textContainer = [[[NSTextContainer alloc]
        initWithContainerSize: NSMakeSize(myWidth, FLT_MAX)] autorelease];
NSLayoutManager *layoutManager = [[[NSLayoutManager alloc] init]
        autorelease];

创建文本对象后,您可以将它们挂钩在一起:

[layoutManager addTextContainer:textContainer];
[textStorage addLayoutManager:layoutManager];

您不需要释放文本容器和布局管理器,因为您在初始化时将它们添加到自动释放池中。
接下来,通过将字体属性添加到文本存储对象中整个字符串的范围来设置字体。
将行片段填充设置为0以获得准确的宽度测量。
(填充用于页面布局,以防止文本容器中的文本与页面上的其他元素(如图形)紧靠。)

[textStorage addAttribute:NSFontAttributeName value:myFont
        range:NSMakeRange(0, [textStorage length])];
[textContainer setLineFragmentPadding:0.0];

最后,因为布局管理器懒惰地执行布局,所以根据需要,您必须强制它布局文本,即使您不需要此函数返回的字形范围。
然后,您可以简单地向布局管理器询问布局文本所占矩形的高度,并假设此代码在函数实现中,返回值:

(void) [layoutManager glyphRangeForTextContainer:textContainer];
return [layoutManager
        usedRectForTextContainer:textContainer].size.height;

八、计算文本行数

此任务展示了如何以编程方式计算文本块中的行数。
行可以由文本字符串中的硬换行符定义,也可以是文本布局机制在包装文本以适应文本容器时生成的行。

由硬换行符定义的文本行,如回车符和换行符,被认为是段落。
也就是说,文本布局引擎生成大小适合文本容器的行片段,直到它到达硬换行符,因此最后一个片段通常短于容器宽度。
但是,如果行被布局到文本容器中,比硬换行符之间最长的字形宽,那么每个段落都是单行。

要计算文本字符串中硬换行符的数量,可以使用NSString方法getLineStart:end:contentsEnd:forRange:lineRangeForRange:
这些方法将字符范围作为输入,并返回包含该范围的行。
它们将行定义为以回车、换行符、回车和换行符一起结束的字符范围(按此顺序,通常称为CRLF),以及用于行分隔符和段落分隔符的Unicode字符。
例如,例1中的代码将NSString变量string中的行数放入numberOfLines


例1 计算硬换行符

NSString *string;
unsigned numberOfLines, index, stringLength = [string length];
for (index = 0, numberOfLines = 0; index < stringLength; numberOfLines++)
    index = NSMaxRange([string lineRangeForRange:NSMakeRange(index, 0)]);

这个紧凑的代码片段首先创建一个仅包含字符串中第一个字符的范围。
lineRangeForRange:方法将包含该字符的行作为包含硬换行符(或多个字符)的范围返回。
NSMaxRange函数返回下一行中第一个字符的索引,即分配给index的值。
numberOfLines变量递增,for循环重复,直到index大于string的长度,此时numberOfLines包含string中的行数,由硬换行符定义。

要计算文本布局机制在包装文本以适应文本容器时生成的行数,您可以从布局管理器获取信息,如例2所示。


例2 包装文本的计数行

NSLayoutManager *layoutManager = [textView layoutManager];
unsigned numberOfLines, index, numberOfGlyphs =
        [layoutManager numberOfGlyphs];
NSRange lineRange;
for (numberOfLines = 0, index = 0; index < numberOfGlyphs; numberOfLines++){
    (void) [layoutManager lineFragmentRectForGlyphAtIndex:index
            effectiveRange:&lineRange];
    index = NSMaxRange(lineRange);
}

此代码假定您对配置有布局管理器、文本存储和文本容器的文本视图有引用。
文本视图返回对布局管理器的引用,然后该管理器返回其关联文本存储中所有字符的字形数,并在必要时执行字形生成。
然后for循环开始布局文本并计算生成的行片段。
NSLayoutManager方法lineFragmentRectForGlyphAtIndex:effectiveRange:强制在传递给它的索引处布局包含字形的行。
该方法返回行片段占用的矩形(此处忽略),并通过引用返回布局后行中字形的范围。
方法计算出一行后,NSMaxRange函数返回大于范围内最大值的索引1,即下一行中第一个字形的索引。
numberOfLines变量递增,for循环重复,直到index大于文本中的字形数,此时numberOfLines包含布局过程产生的行数,由换行定义。

此策略会对文本存储对象中包含的整个文本进行布局,并计算布局所需的行数,而不管填充的文本容器数量或显示它所需的文本视图数量。
要获取单个页面(由文本容器建模)中的行数,您可以使用NSLayoutManager方法glyphRangeForTextContainer:并将行计数for循环限制在该范围内,而不是{0, numberOfGlyphs}范围,该范围包括所有文本。


九、使用文本表

Cocoa文本系统支持OS X 10.4及更高版本中的文本表。
涉及的主要类是NSTextTable,它代表一个表,NSTextTableBlock,它代表一个在表中显示为单元格的文本块,以及它的超类NSTextBlock
本文解释了如何为您的应用程序添加表支持。


1、添加文本表面板

NSTextView内置了对文本表格的支持,它提供了向文本视图添加表格支持的最简单方法。
此表格支持采用action方法的形式orderFrontTablePanel:.此方法将表格插入到文本视图中,并打开漂浮在应用程序窗口上的无模式实用程序窗口。
此表格面板使用户能够在光标或选择位于表格中时操作表格的属性。
表格面板显示在文本表格面板中。


图1 文本表格面板

在这里插入图片描述


用户可以通过直接使用光标操作来更改表的其他方面,例如单元格大小和内容。

要使文本表格面板在文本视图中可用,请使用Interface Builder将orderFrontTablePanel:action方法添加到急救人员,并将其连接到菜单项,如连接操作方法中所示。


图2 连接action方法

在这里插入图片描述


NSTextView为打开列表、链接和段落行间距面板定义了类似的操作方法。


2、以编程方式支持文本表

如果您不想使用文本表格面板,您可以通过直接使用NSTextTable和相关类以编程方式支持表格。
该组中的基本类是NSTextBlock,它表示在文本容器的子区域中布置的文本块。
使用表格时,您可以使用它的子类NSTextTableBlock,它表示在表格中显示为单元格的文本块。
表格本身由一个单独的类NSTextTable表示表格中单元格的所有 NSTextTableBlock对象引用NSTextTable对象,该对象控制它们的大小和位置。

文本块显示为段落的属性,作为段落样式的一部分。
NSParagraphStyle对象可以有一个表示包含段落的表格单元格的文本块数组。
段落样式使用数组,因为表格单元格可以嵌套,文本块在数组中从最外到最内排序。
例如,如果block 1包含四个段落,中间两个也在内部block 2中,那么第一和第四段的文本块数组是(block 1),第二和第三段的数组是(block 1,block 2)。

使用NSMutableParagraphStyle方法setTextBlocks:将文本块添加到段落样式对象setTextBlocks:NSParagraphStyle方法textBlocks返回数组。

要以编程方式实现文本表,请使用以下步骤序列:

  1. 为表创建属性字符串。
  2. 创建表对象,设置列数。
  3. 为行的第一个单元格创建文本表格块,引用表格对象。
  4. 设置文本块的属性。
  5. 为单元格创建段落样式对象,将文本块设置为属性(以及任何其他段落属性,例如对齐方式)。
  6. 为单元格创建属性字符串,添加段落样式作为属性。
    单元格字符串必须以段落标记结尾,例如换行符。
  7. 将单元格字符串附加到表格字符串。
  8. 对表中的每个单元格重复步骤3-7。

在表格创建方法和表格单元格创建方法中显示的方法执行前面列表中的步骤。
(本文中的所有示例方法都在基于文档的应用程序的NSDocument子类中定义,但它们可以很容易地属于另一个对象,例如文本视图。)
表格单元格创建方法对表格中的每个单元格执行步骤3-6,使用胖边框和对比色进行说明。


例1 表创建方法

- (NSMutableAttributedString *) tableAttributedString
{
    // tableString is an ivar declared in the header file as NSMutableAttributedString *tableString;
    tableString = [[NSMutableAttributedString alloc] initWithString:@"\n\n"];
    NSTextTable *table = [[NSTextTable alloc] init];
    [table setNumberOfColumns:2];
 
    [tableString appendAttributedString:[self tableCellAttributedStringWithString:@"Cell1\n"
        table:table
        backgroundColor:[NSColor greenColor]
        borderColor:[NSColor magentaColor]
        row:0
        column:0]];
 
    [tableString appendAttributedString:[self tableCellAttributedStringWithString:@"Cell2\n"
        table:table
        backgroundColor:[NSColor yellowColor]
        borderColor:[NSColor blueColor]
        row:0
        column:1]];
 
    [tableString appendAttributedString:[self tableCellAttributedStringWithString:@"Cell3\n"
        table:table
        backgroundColor:[NSColor lightGrayColor]
        borderColor:[NSColor redColor]
        row:1
        column:0]];
 
    [tableString appendAttributedString:[self tableCellAttributedStringWithString:@"Cell4\n"
        table:table
        backgroundColor:[NSColor cyanColor]
        borderColor:[NSColor orangeColor]
        row:1
        column:1]];
 
    [table release];
    return [tableString autorelease];
}


例2 表格单元格创建方法

- (NSMutableAttributedString *) tableCellAttributedStringWithString:(NSString *)string
        table:(NSTextTable *)table
        backgroundColor:(NSColor *)backgroundColor
        borderColor:(NSColor *)borderColor
        row:(int)row
        column:(int)column
{
    NSTextTableBlock *block = [[NSTextTableBlock alloc]
        initWithTable:table
        startingRow:row
        rowSpan:1
        startingColumn:column
        columnSpan:1];
    [block setBackgroundColor:backgroundColor];
    [block setBorderColor:borderColor];
    [block setWidth:4.0 type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockBorder];
    [block setWidth:6.0 type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding];
 
    NSMutableParagraphStyle *paragraphStyle =
        [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
    [paragraphStyle setTextBlocks:[NSArray arrayWithObjects:block, nil]];
    [block release];
 
    NSMutableAttributedString *cellString =
        [[NSMutableAttributedString alloc] initWithString:string];
    [cellString addAttribute:NSParagraphStyleAttributeName
        value:paragraphStyle
        range:NSMakeRange(0, [cellString length])];
    [paragraphStyle release];
 
    return [cellString autorelease];
}

在表格创建方法和表格单元格创建方法中的代码产生如Table输出所示的表格。


图3 表输出

在这里插入图片描述


要在文本视图中显示的文本中插入表格,请实现一个操作方法,如 例 3中所示。该方法会在文本视图中插入 表格创建方法 中构建的表格字符串,替换当前选区(如果没有选区,则插入点),并确保发送适当的通知和委托消息。


Listing 3 Table insertion action method

- (void) insertMyTable:(id)sender
{
    NSRange charRange = [myTextView rangeForUserTextChange];
    NSTextStorage *myTextStorage = [myTextView textStorage];
 
    if ([myTextView isEditable] && charRange.location != NSNotFound)
        {
            NSMutableAttributedString *attrStringToInsert = [self tableAttributedString];
            if ([myTextView shouldChangeTextInRange:charRange replacementString:nil])
                {
                    [myTextStorage replaceCharactersInRange:charRange
                        withAttributedString:attrStringToInsert];
                    [myTextView setSelectedRange:NSMakeRange(charRange.location, 0)
                        affinity:NSSelectionAffinityUpstream stillSelecting:NO];
                    [myTextView didChangeText];
                }
        }
}

NSAttributedString具有以下方便的方法,您可以使用这些方法来确定文本块或表格所覆盖的字符串范围:

rangeOfTextBlock:atIndex:
rangeOfTextTable:atIndex:

如果给定位置不在指定的块或表中,则这些方法返回一个范围(NSNotFound, 0)


3、文本表模型

Cocoa文本表模型主要源自由超文本标记语言和CSS定义的表模型,其中表是从单元格行构建的。
有关CSS表模型的描述,请参阅以下URL:

http://www.w3.org/TR/CSS21/tables.html

这种关联性提供了另一种在文本中创建表格的方法。
您可以用超文本标记语言定义表格,并使用该数据初始化属性字符串。
然后,该字符串的属性定义由Cocoa文本系统呈现的表格。
为此,您可以使用以下NSAttributedString初始化方法:

initWithHTML:documentAttributes:
initWithHTML:options:documentAttributes:
initWithHTML:baseURL:documentAttributes:
initWithData:options:documentAttributes:error:

4、控制文本块外观

文本块的位置由其文本容器或包含块决定。对于表示表中单元格的文本表块,大小和位置由文本表以及该块与表中其他块的关系控制。当初始化NSTextTableBlock对象时,您将其行和列位置指定为其表中的单元格,还指定它是跨越多行还是多列。NSTextTableBlock初始化方法是:

initWithTable:startingRow:rowSpan:startingColumn:columnSpan:

表格单元格创建方法显示了此方法的使用。

此外,您可以为每个块指定多个维度的值,作为绝对值或包含块的百分比。这些维度包括以下内容:

  • 宽度
  • 高度
  • 最小宽度
  • 最小高度
  • 最大宽度
  • 最大高度
  • 四个边中每个边的填充宽度。填充是包围块内容区域的空间,延伸到边界。
  • 四个边中每个边的边框宽度。边框是填充和边距之间的空间,通常着色以呈现可见的边界。
  • 四个边的边距宽度。边距是边界周围的空间。

这些维度的默认值为0,表示没有填充、边框或边距,以及自然宽度和高度。单个文本块的自然宽度和高度延伸到其包含块(或文本容器)的宽度和高度;多个块的自然宽度和高度均匀地划分其包含块的空间。

以下方法指定或返回与这些维度关联的值:

setValue:type:forDimension:
valueForDimension:
valueTypeForDimension:
setWidth:type:forLayer:
setWidth:type:forLayer:edge:
widthForLayer:edge:
widthValueTypeForLayer:edge:

在这些方法中,值类型是指绝对值或百分比值。维度是指块的最小、最大和全宽和高。层是指填充、边框和边距。这些参数由NSTextBlock中描述的常量指定NSTextBlock

NSTextBlock提供以下方法来指定和返回块的背景和边框颜色:

backgroundColor
setBackgroundColor:
borderColorForEdge:
setBorderColor:forEdge:
setBorderColor:

默认情况下,颜色值为nil,表示没有颜色。请注意,没有颜色的边框是不可见的。


5、表格布局过程

在文本布局期间,排字器使用NSTextBlock来确定文本块的布局矩形。
如果文本块是NSTextTableBlock的实例,它调用其包含的NSTextTable实例来执行计算。
排字器将这些计算的结果存储在其布局管理器中。
NSTextBlockNSTextTableNSLayoutManager中有特定于此布局过程的方法,如果需要干预该过程,可以使用这些方法。

为了开始文本块布局过程,排字者提出一个大矩形,文本块应该适合这个矩形。
对于最外面的块,这是由文本容器决定的;对于内部块,它是由包含块确定的。
然后,块对象决定它应该在提议的矩形中实际占据什么区域。

文本块实际上决定了两个矩形:第一,布局矩形,块中的文本将在其中布局;第二,边界矩形,它包含用于填充、边框、边框装饰和边距的额外空间。
文本块在排字机布置第一个字形之前立即计算布局矩形,因为它是块中所有后续文本布局所必需的。
布局矩形通常相当高,因为此时要布置的文本的高度尚未确定。
文本块在块中最后一个字形布置后立即计算边界矩形,它基于块中文本使用的实际矩形。
在某些情况下,随着同一表中的附加块的布局,边界矩形可能会随后进行调整。

要查找布局和边界矩形,排字器调用以下NSTextBlock方法:

rectForLayoutAtPoint:inRect:textContainer:characterRange:
boundsRectForContentRect:inRect:textContainer:characterRange:

反过来,NSTextTableBlock对象使用以下方法调用其NSTextTable对象来执行这些计算:

rectForBlock:layoutAtPoint:inRect:textContainer:characterRange:
boundsRectForBlock:contentRect:inRect:textContainer:characterRange:

排字器使用以下方法将这些方法的结果存储在布局管理器中:

setLayoutRect:forTextBlock:glyphRange:
setBoundsRect:forTextBlock:glyphRange:

排字器在需要确定先前铺设的文本块使用的空间时使用以下NSLayoutManager方法:

layoutRectForTextBlock:glyphRange:
layoutRectForTextBlock:atIndex:effectiveRange:
boundsRectForTextBlock:glyphRange:
boundsRectForTextBlock:atIndex:effectiveRange:

前面的方法导致字形生成,但不强制布局。
这避免了在布局期间调用方法时的无限递归。
出于同样的原因,现有NSLayoutManager方法的以下变体具有防止它们导致布局的选项:

lineFragmentRectForGlyphAtIndex:effectiveRange:withoutAdditionalLayout:
lineFragmentUsedRectForGlyphAtIndex:effectiveRange:withoutAdditionalLayout:
textContainerForGlyphAtIndex:effectiveRange:withoutAdditionalLayout:

如果未设置矩形,则前面的方法返回NSZeroRect

在显示时,像往常一样绘制文本,如“布局管理器”中所述,除了文本块在绘制字形背景时绘制背景和边框装饰之外,使用以下方法:

drawBackgroundWithFrame:inView:characterRange:layoutManager:

如果文本块是一个NSTextTableBlock对象,它为此目的调用其文本表,使用以下NSTextTable方法:

drawBackgroundForBlock:withFrame:inView:characterRange:layoutManager:

2024-06-18(二)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/759388.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用ioDraw,AI绘图只需几秒钟!

只需几秒钟&#xff0c;就能将文字或图片转化为精准的思维导图、流程图、折线图、柱状图、饼图等各种图表&#xff01; 思维导图 思维导图工具使用入口 文字转思维导图 将文本大纲或想法转换成可视化的思维导图&#xff0c;以组织和结构化您的想法。 图片转思维导图 从现有…

一加12搞机(kernelsu+lsposed)

刷机 温馨提示&#xff1a;如果你不知道root的意义在哪&#xff0c;建议不要解锁和root&#xff0c;到时候救砖或者回锁都挺麻烦。 刷全量包 最新版的系统没有更新推送&#xff0c;所以去一加社区[0]找了个全量包来刷&#xff0c;。安装方式可以看帖子里的内容&#xff0c;说…

XML简介XML 使用教程XML的基本结构XML的使用场景

学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……&#xff09; 2、学会Oracle数据库入门到入土用法(创作中……&#xff09; 3、手把手教你开发炫酷的vbs脚本制作(完善中……&#xff09; 4、牛逼哄哄的 IDEA编程利器技巧(编写中……&#xff09; 5、面经吐血整理的 面试技…

RocketMQ实战:一键在docker中搭建rocketmq和doshboard环境

在本篇博客中&#xff0c;我们将详细介绍如何在 Docker 环境中一键部署 RocketMQ 和其 Dashboard。这个过程基于一个预配置的 Docker Compose 文件&#xff0c;使得部署变得简单高效。 项目介绍 该项目提供了一套 Docker Compose 配置&#xff0c;用于快速部署 RocketMQ 及其…

【图像超分辨率】一个简单的总结

文章目录 图像超分辨率(Image Super-Resolution, ISR)1 什么是图像超分辨率&#xff1f;2 图像超分辨率通常有哪些方法&#xff1f;&#xff08;1&#xff09;基于插值的方法&#xff08;2&#xff09;基于重建的方法&#xff08;3&#xff09;基于学习的方法&#xff08;LR im…

新工具:轻松可视化基因组,部分功能超IGV~

本次分享一个Python基因组数据可视化工具figeno。 figeno擅长可视化三代long reads、跨区域基因组断点视图&#xff08;multi-regions across genomic breakpoints&#xff09;、表观组数据&#xff08;HiC、ATAC-seq和ChIP-seq等&#xff09;可视化、WGS中的CNV和SV可视化等。…

VRay是什么?有什么特点?渲染100邀请码1a12

Vray是由Chaos Group开发的高性能渲染引擎&#xff0c;能为不同的三维建模软件提供图像和动画渲染服务&#xff0c;它有以下几个特点。 1、Vray采用了先进的光线追踪技术&#xff0c;能够模拟真实世界中光线的传播和反射&#xff0c;生成的图像和动画十分逼真。 2、Vray提供了…

通俗范畴论4 范畴的定义

注:由于CSDN无法显示本文章源文件的公式,因此部分下标、字母花体、箭头表示可能会不正常,请读者谅解 范畴的正式定义 上一节我们在没有引入范畴这个数学概念的情况下,直接体验了一个“苹果1”范畴,建立了一个对范畴的直观。本节我们正式学习范畴的定义和基本性质。 一个…

web刷题记录(7)

[HDCTF 2023]SearchMaster 打开环境&#xff0c;首先的提示信息就是告诉我们&#xff0c;可以用post传参的方式来传入参数data 首先考虑的还是rce&#xff0c;但是这里发现&#xff0c;不管输入那种命令&#xff0c;它都会直接显示在中间的那一小行里面&#xff0c;而实际的命令…

centos 破解密码

重启您的CentOS系统。 在GRUB引导加载器启动过程中&#xff0c;当看到启动画面时&#xff0c;按下e键进入编辑模式。 找到以 linux16 或 linux 开头的启动行。 在该行的末尾添加 rd.break 或者ro&#xff08;只读&#xff09;修改为 rw 加init/sysroot/bin/sh参数&#xff0…

HTTPS是什么?原理是什么?用公钥加密为什么不能用公钥解密?

HTTPS&#xff08;HyperText Transfer Protocol Secure&#xff09;是HTTP的安全版本&#xff0c;它通过在HTTP协议之上加入SSL/TLS协议来实现数据加密传输&#xff0c;确保数据在客户端和服务器之间的传输过程中不会被窃取或篡改。 HTTPS 的工作原理 客户端发起HTTPS请求&…

python基础_类

在Python中&#xff0c;类&#xff08;Class&#xff09;是面向对象编程&#xff08;OOP&#xff09;的核心概念之一。类提供了一种创建新对象的模板&#xff0c;这些对象通常被称为类的实例或对象。以下是关于Python类的一些关键点和特性&#xff1a; 定义类 类通过class关键…

深度学习基准模型Transformer

深度学习基准模型Transformer 深度学习基准模型Transformer&#xff0c;最初由Vaswani等人在2017年的论文《Attention is All You Need》中提出&#xff0c;是自然语言处理&#xff08;NLP&#xff09;领域的一个里程碑式模型。它在许多序列到序列&#xff08;seq2seq&#xf…

leetCode.97. 交错字符串

leetCode.97. 交错字符串 题目思路 代码 class Solution { public:bool isInterleave(string s1, string s2, string s3) {int n s1.size(), m s2.size();if ( s3.size() ! n m ) return false;vector<vector<bool>> f( n 1, vector<bool> (m 1));s1 …

C语言刷题小记

前言 本篇博客和大家分享一些C语言的OJ题目&#xff0c;希望大家可以通过这些题目进一步提升自己的编程能力&#xff0c;如果你对本篇内容感兴趣&#xff0c;可以一键三连&#xff0c;多多关注&#xff0c;下面进入正文部分。 题目1 十六进制转十进制 描述 BoBo写了一个十六…

【机器学习】机器学习的重要技术——生成对抗网络:理论、算法与实践

引言 生成对抗网络&#xff08;Generative Adversarial Networks, GANs&#xff09;由Ian Goodfellow等人在2014年提出&#xff0c;通过生成器和判别器两个神经网络的对抗训练&#xff0c;成功实现了高质量数据的生成。GANs在图像生成、数据增强、风格迁移等领域取得了显著成果…

qt文件如何打包成一个独立的exe文件

QT官方给我们安装好了打包软件&#xff0c;就在你QT安装的位置 把这个在cmd打开C:\Qt\6.7.1\mingw_64\bin\windeployqt6.exe&#xff08;或复制地址&#xff09; 然后把要打包项目的exe复制到新的空文件夹&#xff0c;再复制他的地址 按回车后生成新文件 再下载打包软件&#…

使用pyqt5编写一个七彩时钟

使用pyqt5编写一个七彩时钟 效果代码解析定义 RainbowClockWindow 类初始化用户界面显示时间方法 完整代码 在这篇博客中&#xff0c;我们将使用 PyQt5 创建一个简单的七彩数字时钟。 效果 代码解析 定义 RainbowClockWindow 类 class RainbowClockWindow(QMainWindow):def _…

【乐器识别系统】图像识别+人工智能+深度学习+Python+TensorFlow+卷积神经网络+模型训练

一、介绍 乐器识别系统。使用Python为主要编程语言&#xff0c;基于人工智能框架库TensorFlow搭建ResNet50卷积神经网络算法&#xff0c;通过对30种乐器&#xff08;‘迪吉里杜管’, ‘铃鼓’, ‘木琴’, ‘手风琴’, ‘阿尔卑斯号角’, ‘风笛’, ‘班卓琴’, ‘邦戈鼓’, ‘…

Lumos学习王佩丰Excel第三讲:查找替换定位

一、查找和替换 1、按值查找 2、按格式查找 将红色的单元格替换成黄色的单元格&#xff0c;其他格式同理处理。 3、是否开启单元格匹配 若不打开选项卡直接全部替换&#xff0c;会出现“苏州市市”的情况&#xff1b;加入单元格匹配的规则&#xff0c;检索时会以整个单元格内…