1use api::{ColorF, FontInstanceFlags, GlyphInstance, RasterSpace, Shadow};
6use api::units::{LayoutToWorldTransform, LayoutVector2D, RasterPixelScale, DevicePixelScale};
7use crate::scene_building::{CreateShadow, IsVisible};
8use crate::frame_builder::FrameBuildingState;
9use glyph_rasterizer::{FontInstance, FontTransform, GlyphKey, FONT_SIZE_LIMIT};
10use crate::gpu_cache::GpuCache;
11use crate::intern;
12use crate::internal_types::LayoutPrimitiveInfo;
13use crate::picture::SurfaceInfo;
14use crate::prim_store::{PrimitiveOpacity, PrimitiveScratchBuffer};
15use crate::prim_store::{PrimitiveStore, PrimKeyCommonData, PrimTemplateCommonData};
16use crate::renderer::MAX_VERTEX_TEXTURE_WIDTH;
17use crate::resource_cache::ResourceCache;
18use crate::util::MatrixHelpers;
19use crate::prim_store::{InternablePrimitive, PrimitiveInstanceKind};
20use crate::spatial_tree::{SpatialTree, SpatialNodeIndex};
21use crate::space::SpaceSnapper;
22use crate::util::PrimaryArc;
23
24use std::ops;
25use std::sync::Arc;
26
27use super::{storage, VectorKey};
28
29#[cfg_attr(feature = "capture", derive(Serialize))]
31#[cfg_attr(feature = "replay", derive(Deserialize))]
32#[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash)]
33pub struct TextRunKey {
34 pub common: PrimKeyCommonData,
35 pub font: FontInstance,
36 pub glyphs: PrimaryArc<Vec<GlyphInstance>>,
37 pub shadow: bool,
38 pub requested_raster_space: RasterSpace,
39 pub reference_frame_offset: VectorKey,
40}
41
42impl TextRunKey {
43 pub fn new(
44 info: &LayoutPrimitiveInfo,
45 text_run: TextRun,
46 ) -> Self {
47 TextRunKey {
48 common: info.into(),
49 font: text_run.font,
50 glyphs: PrimaryArc(text_run.glyphs),
51 shadow: text_run.shadow,
52 requested_raster_space: text_run.requested_raster_space,
53 reference_frame_offset: text_run.reference_frame_offset.into(),
54 }
55 }
56}
57
58impl intern::InternDebug for TextRunKey {}
59
60#[cfg_attr(feature = "capture", derive(Serialize))]
61#[cfg_attr(feature = "replay", derive(Deserialize))]
62#[derive(MallocSizeOf)]
63pub struct TextRunTemplate {
64 pub common: PrimTemplateCommonData,
65 pub font: FontInstance,
66 #[ignore_malloc_size_of = "Measured via PrimaryArc"]
67 pub glyphs: Arc<Vec<GlyphInstance>>,
68}
69
70impl ops::Deref for TextRunTemplate {
71 type Target = PrimTemplateCommonData;
72 fn deref(&self) -> &Self::Target {
73 &self.common
74 }
75}
76
77impl ops::DerefMut for TextRunTemplate {
78 fn deref_mut(&mut self) -> &mut Self::Target {
79 &mut self.common
80 }
81}
82
83impl From<TextRunKey> for TextRunTemplate {
84 fn from(item: TextRunKey) -> Self {
85 let common = PrimTemplateCommonData::with_key_common(item.common);
86 TextRunTemplate {
87 common,
88 font: item.font,
89 glyphs: item.glyphs.0,
90 }
91 }
92}
93
94impl TextRunTemplate {
95 pub fn update(
100 &mut self,
101 frame_state: &mut FrameBuildingState,
102 ) {
103 self.write_prim_gpu_blocks(frame_state);
104 self.opacity = PrimitiveOpacity::translucent();
105 }
106
107 fn write_prim_gpu_blocks(
108 &mut self,
109 frame_state: &mut FrameBuildingState,
110 ) {
111 if let Some(mut request) = frame_state.gpu_cache.request(&mut self.common.gpu_cache_handle) {
113 request.push(ColorF::from(self.font.color).premultiplied());
114
115 let mut gpu_block = [0.0; 4];
116 for (i, src) in self.glyphs.iter().enumerate() {
117 if (i & 1) == 0 {
120 gpu_block[0] = src.point.x;
121 gpu_block[1] = src.point.y;
122 } else {
123 gpu_block[2] = src.point.x;
124 gpu_block[3] = src.point.y;
125 request.push(gpu_block);
126 }
127 }
128
129 if (self.glyphs.len() & 1) != 0 {
132 request.push(gpu_block);
133 }
134
135 assert!(request.current_used_block_num() <= MAX_VERTEX_TEXTURE_WIDTH);
136 }
137 }
138}
139
140pub type TextRunDataHandle = intern::Handle<TextRun>;
141
142#[derive(Debug, MallocSizeOf)]
143#[cfg_attr(feature = "capture", derive(Serialize))]
144#[cfg_attr(feature = "replay", derive(Deserialize))]
145pub struct TextRun {
146 pub font: FontInstance,
147 #[ignore_malloc_size_of = "Measured via PrimaryArc"]
148 pub glyphs: Arc<Vec<GlyphInstance>>,
149 pub shadow: bool,
150 pub requested_raster_space: RasterSpace,
151 pub reference_frame_offset: LayoutVector2D,
152}
153
154impl intern::Internable for TextRun {
155 type Key = TextRunKey;
156 type StoreData = TextRunTemplate;
157 type InternData = ();
158 const PROFILE_COUNTER: usize = crate::profiler::INTERNED_TEXT_RUNS;
159}
160
161impl InternablePrimitive for TextRun {
162 fn into_key(
163 self,
164 info: &LayoutPrimitiveInfo,
165 ) -> TextRunKey {
166 TextRunKey::new(
167 info,
168 self,
169 )
170 }
171
172 fn make_instance_kind(
173 key: TextRunKey,
174 data_handle: TextRunDataHandle,
175 prim_store: &mut PrimitiveStore,
176 ) -> PrimitiveInstanceKind {
177 let reference_frame_offset = key.reference_frame_offset.into();
178
179 let run_index = prim_store.text_runs.push(TextRunPrimitive {
180 used_font: key.font.clone(),
181 glyph_keys_range: storage::Range::empty(),
182 reference_frame_relative_offset: reference_frame_offset,
183 snapped_reference_frame_relative_offset: reference_frame_offset,
184 shadow: key.shadow,
185 raster_scale: 1.0,
186 requested_raster_space: key.requested_raster_space,
187 });
188
189 PrimitiveInstanceKind::TextRun{ data_handle, run_index }
190 }
191}
192
193impl CreateShadow for TextRun {
194 fn create_shadow(
195 &self,
196 shadow: &Shadow,
197 blur_is_noop: bool,
198 current_raster_space: RasterSpace,
199 ) -> Self {
200 let mut font = FontInstance {
201 color: shadow.color.into(),
202 ..self.font.clone()
203 };
204 if shadow.blur_radius > 0.0 {
205 font.disable_subpixel_aa();
206 }
207
208 let requested_raster_space = if blur_is_noop {
209 current_raster_space
210 } else {
211 RasterSpace::Local(1.0)
212 };
213
214 TextRun {
215 font,
216 glyphs: self.glyphs.clone(),
217 shadow: true,
218 requested_raster_space,
219 reference_frame_offset: self.reference_frame_offset,
220 }
221 }
222}
223
224impl IsVisible for TextRun {
225 fn is_visible(&self) -> bool {
226 self.font.color.a > 0
227 }
228}
229
230#[derive(Debug)]
231#[cfg_attr(feature = "capture", derive(Serialize))]
232pub struct TextRunPrimitive {
233 pub used_font: FontInstance,
234 pub glyph_keys_range: storage::Range<GlyphKey>,
235 pub reference_frame_relative_offset: LayoutVector2D,
236 pub snapped_reference_frame_relative_offset: LayoutVector2D,
237 pub shadow: bool,
238 pub raster_scale: f32,
239 pub requested_raster_space: RasterSpace,
240}
241
242impl TextRunPrimitive {
243 pub fn update_font_instance(
244 &mut self,
245 specified_font: &FontInstance,
246 surface: &SurfaceInfo,
247 spatial_node_index: SpatialNodeIndex,
248 transform: &LayoutToWorldTransform,
249 allow_subpixel: bool,
250 raster_space: RasterSpace,
251 spatial_tree: &SpatialTree,
252 ) -> bool {
253 let raster_scale = raster_space.local_scale().unwrap_or(1.0).max(0.001);
260
261 let dps = surface.device_pixel_scale.0;
262 let font_size = specified_font.size.to_f32_px();
263
264 let quantized_scale = (dps * raster_scale * 100.0).round() / 100.0;
269 let mut device_font_size = font_size * quantized_scale;
270
271 let (use_subpixel_aa, transform_glyphs, texture_padding, oversized) = if raster_space != RasterSpace::Screen ||
277 transform.has_perspective_component() || !transform.has_2d_inverse()
278 {
279 (false, false, true, device_font_size > FONT_SIZE_LIMIT)
280 } else if transform.exceeds_2d_scale((FONT_SIZE_LIMIT / device_font_size) as f64) {
281 (false, false, true, true)
282 } else {
283 (true, !transform.is_simple_2d_translation(), false, false)
284 };
285
286 let font_transform = if transform_glyphs {
287 self.raster_scale = 1.0;
290 FontTransform::from(transform)
291 } else {
292 if oversized {
293 let limited_raster_scale = FONT_SIZE_LIMIT / (font_size * dps);
297 device_font_size = FONT_SIZE_LIMIT;
298
299 self.raster_scale = limited_raster_scale;
302 } else {
303 self.raster_scale = raster_scale;
307 }
308
309 FontTransform::identity()
311 };
312
313 self.snapped_reference_frame_relative_offset = if transform_glyphs {
322 self.reference_frame_relative_offset
325 } else {
326 let raster_pixel_scale = RasterPixelScale::new(surface.device_pixel_scale.0);
330
331 let snap_to_device = SpaceSnapper::new_with_target(
334 surface.raster_spatial_node_index,
335 spatial_node_index,
336 raster_pixel_scale,
337 spatial_tree,
338 );
339 snap_to_device.snap_point(&self.reference_frame_relative_offset.to_point()).to_vector()
340 };
341
342 let mut flags = specified_font.flags;
343 if transform_glyphs {
344 flags |= FontInstanceFlags::TRANSFORM_GLYPHS;
345 }
346 if texture_padding {
347 flags |= FontInstanceFlags::TEXTURE_PADDING;
348 }
349
350 let cache_dirty =
353 self.used_font.transform != font_transform ||
354 self.used_font.size != device_font_size.into() ||
355 self.used_font.flags != flags;
356
357 self.used_font = FontInstance {
359 transform: font_transform,
360 size: device_font_size.into(),
361 flags,
362 ..specified_font.clone()
363 };
364
365 if !allow_subpixel || !use_subpixel_aa {
367 self.used_font.disable_subpixel_aa();
368
369 if oversized {
375 self.used_font.disable_subpixel_position();
376 }
377 }
378
379 cache_dirty
380 }
381
382 fn get_raster_space_for_prim(
388 &self,
389 prim_spatial_node_index: SpatialNodeIndex,
390 low_quality_pinch_zoom: bool,
391 device_pixel_scale: DevicePixelScale,
392 spatial_tree: &SpatialTree,
393 ) -> RasterSpace {
394 let prim_spatial_node = spatial_tree.get_spatial_node(prim_spatial_node_index);
395 if prim_spatial_node.is_ancestor_or_self_zooming {
396 if low_quality_pinch_zoom {
397 RasterSpace::Local(1.0)
404 } else {
405 let root_spatial_node_index = spatial_tree.root_reference_frame_index();
406
407 let scale_factors = spatial_tree
412 .get_relative_transform(prim_spatial_node_index, root_spatial_node_index)
413 .scale_factors();
414
415 let scale = scale_factors.0.max(scale_factors.1).min(8.0).max(1.0);
417 let rounded_up = 2.0f32.powf(scale.log2().ceil());
418
419 RasterSpace::Local(rounded_up / device_pixel_scale.0)
420 }
421 } else {
422 match self.requested_raster_space {
425 RasterSpace::Local(scale) => RasterSpace::Local(scale / device_pixel_scale.0),
426 RasterSpace::Screen => RasterSpace::Screen,
427 }
428 }
429 }
430
431 pub fn request_resources(
432 &mut self,
433 prim_offset: LayoutVector2D,
434 specified_font: &FontInstance,
435 glyphs: &[GlyphInstance],
436 transform: &LayoutToWorldTransform,
437 surface: &SurfaceInfo,
438 spatial_node_index: SpatialNodeIndex,
439 allow_subpixel: bool,
440 low_quality_pinch_zoom: bool,
441 resource_cache: &mut ResourceCache,
442 gpu_cache: &mut GpuCache,
443 spatial_tree: &SpatialTree,
444 scratch: &mut PrimitiveScratchBuffer,
445 ) {
446 let raster_space = self.get_raster_space_for_prim(
447 spatial_node_index,
448 low_quality_pinch_zoom,
449 surface.device_pixel_scale,
450 spatial_tree,
451 );
452
453 let cache_dirty = self.update_font_instance(
454 specified_font,
455 surface,
456 spatial_node_index,
457 transform,
458 allow_subpixel,
459 raster_space,
460 spatial_tree,
461 );
462
463 if self.glyph_keys_range.is_empty() || cache_dirty {
464 let subpx_dir = self.used_font.get_subpx_dir();
465
466 let dps = surface.device_pixel_scale.0;
467 let transform = match raster_space {
468 RasterSpace::Local(scale) => FontTransform::new(scale * dps, 0.0, 0.0, scale * dps),
469 RasterSpace::Screen => self.used_font.transform.scale(dps),
470 };
471
472 self.glyph_keys_range = scratch.glyph_keys.extend(
473 glyphs.iter().map(|src| {
474 let src_point = src.point + prim_offset;
475 let device_offset = transform.transform(&src_point);
476 GlyphKey::new(src.index, device_offset, subpx_dir)
477 }));
478 }
479
480 resource_cache.request_glyphs(
481 self.used_font.clone(),
482 &scratch.glyph_keys[self.glyph_keys_range],
483 gpu_cache,
484 );
485 }
486}
487
488#[test]
490#[cfg(target_os = "linux")]
491fn test_struct_sizes() {
492 use std::mem;
493 assert_eq!(mem::size_of::<TextRun>(), 72, "TextRun size changed");
500 assert_eq!(mem::size_of::<TextRunTemplate>(), 80, "TextRunTemplate size changed");
501 assert_eq!(mem::size_of::<TextRunKey>(), 88, "TextRunKey size changed");
502 assert_eq!(mem::size_of::<TextRunPrimitive>(), 80, "TextRunPrimitive size changed");
503}