1use crate::TextFontService;
46use crate::font::resolve::resolve_font;
47use crate::layout::block::BlockLayoutParams;
48use crate::layout::flow::{FlowItem, FlowLayout};
49use crate::layout::frame::FrameLayoutParams;
50use crate::layout::inline_markup::{InlineAttrs, InlineMarkup};
51use crate::layout::paragraph::{Alignment, Hyphenator, break_into_lines};
52use crate::layout::table::TableLayoutParams;
53use crate::shaping::run::{ShapedGlyph, ShapedRun};
54use crate::shaping::shaper::{
55 bidi_runs, font_metrics_px, shape_text, shape_text_with_fallback, to_harfrust_features,
56};
57use crate::types::{
58 BlockVisualInfo, CharacterGeometry, CursorDisplay, DecorationKind, DecorationRect, GlyphQuad,
59 HitTestResult, LaidOutSpan, LaidOutSpanKind, ParagraphResult, RenderFrame, SingleLineResult,
60 TextFormat,
61};
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum RelayoutError {
75 NoLayout,
80 ScaleDirty,
88}
89
90impl std::fmt::Display for RelayoutError {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 match self {
93 RelayoutError::NoLayout => {
94 f.write_str("relayout_block called before any layout_* method")
95 }
96 RelayoutError::ScaleDirty => f.write_str(
97 "relayout_block called after a scale-factor change without a fresh layout_*",
98 ),
99 }
100 }
101}
102
103impl std::error::Error for RelayoutError {}
104
105#[derive(Debug, Clone, Copy, Default)]
110pub enum ContentWidthMode {
111 #[default]
115 Auto,
116 Fixed(f32),
120}
121
122pub struct DocumentFlow {
129 flow_layout: FlowLayout,
130 render_frame: RenderFrame,
131 scroll_offset: f32,
132 rendered_scroll_offset: f32,
133 viewport_width: f32,
134 viewport_height: f32,
135 content_width_mode: ContentWidthMode,
136 selection_color: [f32; 4],
137 cursor_color: [f32; 4],
138 text_color: [f32; 4],
139 code_block_background: [f32; 4],
145 code_block_foreground: Option<[f32; 4]>,
150 echo_char: Option<char>,
157 hyphenate_justified: bool,
163 cursors: Vec<CursorDisplay>,
164 zoom: f32,
165 rendered_zoom: f32,
166 font_scale: f32,
175 raster_scale: f32,
184 rendered_raster_scale: f32,
185 layout_scale_generation: u64,
191 has_layout: bool,
193}
194
195impl DocumentFlow {
196 pub fn new() -> Self {
202 Self {
203 flow_layout: FlowLayout::new(),
204 render_frame: RenderFrame::new(),
205 scroll_offset: 0.0,
206 rendered_scroll_offset: f32::NAN,
207 viewport_width: 0.0,
208 viewport_height: 0.0,
209 content_width_mode: ContentWidthMode::Auto,
210 selection_color: [0.26, 0.52, 0.96, 0.3],
211 cursor_color: [0.0, 0.0, 0.0, 1.0],
212 text_color: [0.0, 0.0, 0.0, 1.0],
213 code_block_background: [0.95, 0.95, 0.95, 1.0],
214 code_block_foreground: None,
215 echo_char: None,
216 hyphenate_justified: false,
217 cursors: Vec::new(),
218 zoom: 1.0,
219 rendered_zoom: f32::NAN,
220 font_scale: 1.0,
221 raster_scale: 1.0,
222 rendered_raster_scale: f32::NAN,
223 layout_scale_generation: 0,
224 has_layout: false,
225 }
226 }
227
228 pub fn set_viewport(&mut self, width: f32, height: f32) {
244 self.viewport_width = width;
245 self.viewport_height = height;
246 self.flow_layout.viewport_width = width;
247 self.flow_layout.viewport_height = height;
248 }
249
250 pub fn viewport_width(&self) -> f32 {
252 self.viewport_width
253 }
254
255 pub fn viewport_height(&self) -> f32 {
257 self.viewport_height
258 }
259
260 pub fn set_content_width(&mut self, width: f32) {
266 self.content_width_mode = ContentWidthMode::Fixed(width);
267 }
268
269 pub fn set_content_width_auto(&mut self) {
274 self.content_width_mode = ContentWidthMode::Auto;
275 }
276
277 pub fn layout_width(&self) -> f32 {
285 match self.content_width_mode {
286 ContentWidthMode::Auto => self.viewport_width / self.zoom,
287 ContentWidthMode::Fixed(w) => w,
288 }
289 }
290
291 pub fn content_width_mode(&self) -> ContentWidthMode {
293 self.content_width_mode
294 }
295
296 pub fn set_scroll_offset(&mut self, offset: f32) {
300 self.scroll_offset = offset;
301 }
302
303 pub fn scroll_offset(&self) -> f32 {
305 self.scroll_offset
306 }
307
308 pub fn content_height(&self) -> f32 {
310 self.flow_layout.content_height
311 }
312
313 pub fn max_content_width(&self) -> f32 {
317 self.flow_layout.cached_max_content_width
318 }
319
320 pub fn set_zoom(&mut self, zoom: f32) {
334 self.zoom = zoom.clamp(0.1, 10.0);
335 }
336
337 pub fn zoom(&self) -> f32 {
339 self.zoom
340 }
341
342 pub fn set_font_scale(&mut self, font_scale: f32) {
354 self.font_scale = font_scale.clamp(0.1, 10.0);
355 }
356
357 pub fn font_scale(&self) -> f32 {
359 self.font_scale
360 }
361
362 pub fn set_raster_scale(&mut self, raster_scale: f32) {
376 self.raster_scale = raster_scale.clamp(0.1, 16.0);
377 }
378
379 pub fn raster_scale(&self) -> f32 {
381 self.raster_scale
382 }
383
384 pub fn has_layout(&self) -> bool {
391 self.has_layout
392 }
393
394 pub fn layout_dirty_for_scale(&self, service: &TextFontService) -> bool {
404 self.has_layout && self.layout_scale_generation != service.scale_generation()
405 }
406
407 #[cfg(feature = "text-document")]
416 pub fn layout_full(&mut self, service: &TextFontService, flow: &text_document::FlowSnapshot) {
417 use crate::bridge::{BridgeOptions, convert_flow_with};
418
419 let opts = BridgeOptions {
420 code_block_background: self.code_block_background,
421 code_block_foreground: self.code_block_foreground,
422 echo_char: self.echo_char,
423 hyphenate_justified: self.hyphenate_justified,
424 };
425 let converted = convert_flow_with(flow, &opts);
426
427 let mut all_items: Vec<(usize, FlowItemKind)> = Vec::new();
429 for (idx, params) in converted.blocks {
430 all_items.push((idx, FlowItemKind::Block(params)));
431 }
432 for (idx, params) in converted.tables {
433 all_items.push((idx, FlowItemKind::Table(params)));
434 }
435 for (idx, params) in converted.frames {
436 all_items.push((idx, FlowItemKind::Frame(params)));
437 }
438 all_items.sort_by_key(|(idx, _)| *idx);
439
440 let lw = self.layout_width();
441 self.flow_layout.clear();
442 self.flow_layout.viewport_width = self.viewport_width;
443 self.flow_layout.viewport_height = self.viewport_height;
444 self.flow_layout.scale_factor = service.scale_factor;
445 self.flow_layout.font_scale = self.font_scale;
446
447 for (_idx, kind) in all_items {
448 match kind {
449 FlowItemKind::Block(params) => {
450 self.flow_layout
451 .add_block(&service.font_registry, ¶ms, lw);
452 }
453 FlowItemKind::Table(params) => {
454 self.flow_layout
455 .add_table(&service.font_registry, ¶ms, lw);
456 }
457 FlowItemKind::Frame(params) => {
458 self.flow_layout
459 .add_frame(&service.font_registry, ¶ms, lw);
460 }
461 }
462 }
463
464 self.flow_layout.refresh_base_blocks();
467
468 self.note_layout_done(service);
469 }
470
471 pub fn layout_blocks(
477 &mut self,
478 service: &TextFontService,
479 block_params: Vec<BlockLayoutParams>,
480 ) {
481 self.flow_layout.scale_factor = service.scale_factor;
482 self.flow_layout.font_scale = self.font_scale;
483 self.flow_layout
484 .layout_blocks(&service.font_registry, block_params, self.layout_width());
485 self.note_layout_done(service);
486 }
487
488 pub fn add_frame(&mut self, service: &TextFontService, params: &FrameLayoutParams) {
491 self.flow_layout.scale_factor = service.scale_factor;
492 self.flow_layout.font_scale = self.font_scale;
493 self.flow_layout
494 .add_frame(&service.font_registry, params, self.layout_width());
495 self.note_layout_done(service);
496 }
497
498 pub fn add_table(&mut self, service: &TextFontService, params: &TableLayoutParams) {
500 self.flow_layout.scale_factor = service.scale_factor;
501 self.flow_layout.font_scale = self.font_scale;
502 self.flow_layout
503 .add_table(&service.font_registry, params, self.layout_width());
504 self.note_layout_done(service);
505 }
506
507 pub fn relayout_block(
538 &mut self,
539 service: &TextFontService,
540 params: &BlockLayoutParams,
541 ) -> Result<(), RelayoutError> {
542 if !self.has_layout {
543 return Err(RelayoutError::NoLayout);
544 }
545 if self.layout_scale_generation != service.scale_generation() {
546 return Err(RelayoutError::ScaleDirty);
547 }
548 self.flow_layout.scale_factor = service.scale_factor;
549 self.flow_layout.font_scale = self.font_scale;
550 self.flow_layout
551 .relayout_block(&service.font_registry, params, self.layout_width());
552 self.note_layout_done(service);
553 Ok(())
554 }
555
556 pub fn apply_paint_spans_for(
561 &mut self,
562 spans_by_block: std::collections::HashMap<usize, Vec<crate::layout::block::PaintSpan>>,
563 ) {
564 self.flow_layout.apply_paint_spans_for(spans_by_block);
565 }
566
567 pub fn apply_block_paint_spans(
570 &mut self,
571 block_id: usize,
572 spans: &[crate::layout::block::PaintSpan],
573 ) -> bool {
574 self.flow_layout.apply_block_paint_spans(block_id, spans)
575 }
576
577 fn note_layout_done(&mut self, service: &TextFontService) {
578 self.has_layout = true;
579 self.layout_scale_generation = service.scale_generation();
580 }
581
582 pub fn render(&mut self, service: &mut TextFontService) -> &RenderFrame {
594 let effective_vw = self.viewport_width / self.zoom;
595 let effective_vh = self.viewport_height / self.zoom;
596 crate::render::frame::build_render_frame(
597 &self.flow_layout,
598 &service.font_registry,
599 &mut service.atlas,
600 &mut service.glyph_cache,
601 &mut service.scale_context,
602 self.scroll_offset,
603 effective_vw,
604 effective_vh,
605 &self.cursors,
606 self.cursor_color,
607 self.selection_color,
608 self.text_color,
609 self.raster_scale,
610 &mut self.render_frame,
611 &mut service.eviction_epoch,
612 );
613 self.rendered_scroll_offset = self.scroll_offset;
614 self.rendered_zoom = self.zoom;
615 self.rendered_raster_scale = self.raster_scale;
616 apply_zoom(&mut self.render_frame, self.zoom);
617 &self.render_frame
618 }
619
620 pub fn render_block_only(
633 &mut self,
634 service: &mut TextFontService,
635 block_id: usize,
636 ) -> &RenderFrame {
637 if (self.scroll_offset - self.rendered_scroll_offset).abs() > 0.001
638 || (self.zoom - self.rendered_zoom).abs() > 0.001
639 || (self.raster_scale - self.rendered_raster_scale).abs() > 0.001
640 {
641 return self.render(service);
642 }
643
644 if service.eviction_epoch != self.render_frame.atlas_eviction_epoch {
650 return self.render(service);
651 }
652
653 if !self.flow_layout.blocks.contains_key(&block_id) {
654 let in_table = self.flow_layout.tables.values().any(|table| {
655 table
656 .cell_layouts
657 .iter()
658 .any(|c| c.blocks.iter().any(|b| b.block_id == block_id))
659 });
660 if in_table {
661 return self.render(service);
662 }
663 let in_frame = self
664 .flow_layout
665 .frames
666 .values()
667 .any(|frame| crate::layout::flow::frame_contains_block(frame, block_id));
668 if in_frame {
669 return self.render(service);
670 }
671 }
672
673 if let Some(block) = self.flow_layout.blocks.get(&block_id) {
674 let old_height = self
675 .render_frame
676 .block_heights
677 .get(&block_id)
678 .copied()
679 .unwrap_or(block.height);
680 if (block.height - old_height).abs() > 0.001 {
681 return self.render(service);
682 }
683 }
684
685 let effective_vw = self.viewport_width / self.zoom;
686 let effective_vh = self.viewport_height / self.zoom;
687 let scale_factor = service.scale_factor;
688 let mut new_glyphs = Vec::new();
689 let mut new_images = Vec::new();
690 let mut new_keys: Vec<crate::atlas::cache::GlyphCacheKey> = Vec::new();
691 if let Some(block) = self.flow_layout.blocks.get(&block_id) {
692 let mut tmp = RenderFrame::new();
693 crate::render::frame::render_block_at_offset(
694 block,
695 0.0,
696 0.0,
697 &service.font_registry,
698 &mut service.atlas,
699 &mut service.glyph_cache,
700 &mut service.scale_context,
701 self.scroll_offset,
702 effective_vh,
703 self.text_color,
704 scale_factor,
705 self.raster_scale,
706 &mut tmp,
707 &mut new_keys,
708 &mut service.eviction_epoch,
709 );
710 new_glyphs = tmp.glyphs;
711 new_images = tmp.images;
712 }
713
714 let new_decos = if let Some(block) = self.flow_layout.blocks.get(&block_id) {
715 crate::render::decoration::generate_block_decorations(
716 block,
717 &service.font_registry,
718 self.scroll_offset,
719 effective_vh,
720 0.0,
721 0.0,
722 effective_vw,
723 self.text_color,
724 scale_factor,
725 )
726 } else {
727 Vec::new()
728 };
729
730 if let Some(entry) = self
731 .render_frame
732 .block_glyphs
733 .iter_mut()
734 .find(|(id, _)| *id == block_id)
735 {
736 entry.1 = new_glyphs;
737 }
738 if let Some(entry) = self
739 .render_frame
740 .block_images
741 .iter_mut()
742 .find(|(id, _)| *id == block_id)
743 {
744 entry.1 = new_images;
745 }
746 if let Some(entry) = self
747 .render_frame
748 .block_decorations
749 .iter_mut()
750 .find(|(id, _)| *id == block_id)
751 {
752 entry.1 = new_decos;
753 }
754 if let Some(entry) = self
755 .render_frame
756 .block_glyph_keys
757 .iter_mut()
758 .find(|(id, _)| *id == block_id)
759 {
760 entry.1 = new_keys;
761 }
762
763 self.rebuild_flat_frame(service);
764 apply_zoom(&mut self.render_frame, self.zoom);
765 &self.render_frame
766 }
767
768 pub fn render_cursor_only(&mut self, service: &mut TextFontService) -> &RenderFrame {
776 if (self.scroll_offset - self.rendered_scroll_offset).abs() > 0.001
777 || (self.zoom - self.rendered_zoom).abs() > 0.001
778 {
779 return self.render(service);
780 }
781
782 if service.eviction_epoch != self.render_frame.atlas_eviction_epoch {
787 return self.render(service);
788 }
789
790 service.touch_glyphs(&self.render_frame.glyph_keys);
798
799 self.render_frame.decorations.retain(|d| {
800 !matches!(
801 d.kind,
802 DecorationKind::Cursor | DecorationKind::Selection | DecorationKind::CellSelection
803 )
804 });
805
806 let effective_vw = self.viewport_width / self.zoom;
807 let effective_vh = self.viewport_height / self.zoom;
808 let mut cursor_decos = crate::render::cursor::generate_cursor_decorations(
809 &self.flow_layout,
810 &self.cursors,
811 self.scroll_offset,
812 self.cursor_color,
813 self.selection_color,
814 effective_vw,
815 effective_vh,
816 );
817 apply_zoom_decorations(&mut cursor_decos, self.zoom);
818 self.render_frame.decorations.extend(cursor_decos);
819
820 &self.render_frame
821 }
822
823 fn rebuild_flat_frame(&mut self, service: &mut TextFontService) {
824 self.render_frame.glyphs.clear();
825 self.render_frame.images.clear();
826 self.render_frame.decorations.clear();
827 self.render_frame.glyph_keys.clear();
828 for (_, glyphs) in &self.render_frame.block_glyphs {
829 self.render_frame.glyphs.extend_from_slice(glyphs);
830 }
831 for (_, images) in &self.render_frame.block_images {
832 self.render_frame.images.extend_from_slice(images);
833 }
834 for (_, decos) in &self.render_frame.block_decorations {
835 self.render_frame.decorations.extend_from_slice(decos);
836 }
837 for (_, keys) in &self.render_frame.block_glyph_keys {
838 self.render_frame.glyph_keys.extend_from_slice(keys);
839 }
840 service.touch_glyphs(&self.render_frame.glyph_keys);
847
848 for item in &self.flow_layout.flow_order {
849 match item {
850 FlowItem::Table { table_id, .. } => {
851 if let Some(table) = self.flow_layout.tables.get(table_id) {
852 let decos = crate::layout::table::generate_table_decorations(
853 table,
854 self.scroll_offset,
855 );
856 self.render_frame.decorations.extend(decos);
857 }
858 }
859 FlowItem::Frame { frame_id, .. } => {
860 if let Some(frame) = self.flow_layout.frames.get(frame_id) {
861 crate::render::frame::append_frame_table_decorations(
862 frame,
863 0.0,
864 0.0,
865 self.scroll_offset,
866 &mut self.render_frame.decorations,
867 );
868 crate::render::frame::append_frame_border_decorations(
869 frame,
870 self.scroll_offset,
871 &mut self.render_frame.decorations,
872 );
873 }
874 }
875 FlowItem::Block { .. } => {}
876 }
877 }
878
879 let effective_vw = self.viewport_width / self.zoom;
880 let effective_vh = self.viewport_height / self.zoom;
881 let cursor_decos = crate::render::cursor::generate_cursor_decorations(
882 &self.flow_layout,
883 &self.cursors,
884 self.scroll_offset,
885 self.cursor_color,
886 self.selection_color,
887 effective_vw,
888 effective_vh,
889 );
890 self.render_frame.decorations.extend(cursor_decos);
891
892 self.render_frame.atlas_dirty = service.atlas.dirty;
893 self.render_frame.atlas_width = service.atlas.width;
894 self.render_frame.atlas_height = service.atlas.height;
895 if service.atlas.dirty {
896 let pixels = &service.atlas.pixels;
897 let needed = (service.atlas.width * service.atlas.height * 4) as usize;
898 self.render_frame.atlas_pixels.resize(needed, 0);
899 let copy_len = needed.min(pixels.len());
900 self.render_frame.atlas_pixels[..copy_len].copy_from_slice(&pixels[..copy_len]);
901 service.atlas.dirty = false;
902 }
903 }
904
905 pub fn layout_single_line(
921 &mut self,
922 service: &mut TextFontService,
923 text: &str,
924 format: &TextFormat,
925 max_width: Option<f32>,
926 raster_scale: f32,
927 ) -> SingleLineResult {
928 let empty = SingleLineResult {
929 width: 0.0,
930 height: 0.0,
931 baseline: 0.0,
932 underline_offset: 0.0,
933 underline_thickness: 0.0,
934 glyphs: Vec::new(),
935 glyph_keys: Vec::new(),
936 spans: Vec::new(),
937 };
938
939 if text.is_empty() {
940 return empty;
941 }
942
943 let font_point_size = format.font_size.map(|s| s as u32);
944 let resolved = match resolve_font(
945 &service.font_registry,
946 format.font_family.as_deref(),
947 format.font_weight,
948 format.font_bold,
949 format.font_italic,
950 font_point_size,
951 service.scale_factor,
952 1.0, ) {
954 Some(r) => r,
955 None => return empty,
956 };
957
958 let metrics = match font_metrics_px(&service.font_registry, &resolved) {
959 Some(m) => m,
960 None => return empty,
961 };
962 let line_height = metrics.ascent + metrics.descent + metrics.leading;
963 let baseline = metrics.ascent;
964
965 let features = to_harfrust_features(&format.features);
966 let runs: Vec<_> = bidi_runs(text)
967 .into_iter()
968 .filter_map(|br| {
969 let slice = text.get(br.byte_range.clone())?;
970 shape_text_with_fallback(
971 &service.font_registry,
972 &resolved,
973 slice,
974 br.byte_range.start,
975 br.direction,
976 &features,
977 )
978 })
979 .collect();
980
981 if runs.is_empty() {
982 return empty;
983 }
984
985 let total_advance: f32 = runs.iter().map(|r| r.advance_width).sum();
986
987 let (truncate_at_visual_index, final_width, ellipsis_run) = if let Some(max_w) = max_width
988 && total_advance > max_w
989 {
990 let ellipsis_run = shape_text(&service.font_registry, &resolved, "\u{2026}", 0);
991 let ellipsis_width = ellipsis_run
992 .as_ref()
993 .map(|r| r.advance_width)
994 .unwrap_or(0.0);
995 let budget = (max_w - ellipsis_width).max(0.0);
996
997 let mut used = 0.0f32;
998 let mut count = 0usize;
999 'outer: for run in &runs {
1000 for g in &run.glyphs {
1001 if used + g.x_advance > budget {
1002 break 'outer;
1003 }
1004 used += g.x_advance;
1005 count += 1;
1006 }
1007 }
1008
1009 (Some(count), used + ellipsis_width, ellipsis_run)
1010 } else {
1011 (None, total_advance, None)
1012 };
1013
1014 let text_color = format.color.unwrap_or(self.text_color);
1015 let glyph_capacity: usize = runs.iter().map(|r| r.glyphs.len()).sum();
1016 let mut quads = Vec::with_capacity(glyph_capacity + 1);
1017 let mut keys = Vec::with_capacity(glyph_capacity + 1);
1018 let mut pen_x = 0.0f32;
1019 let mut emitted = 0usize;
1020
1021 'emit: for run in &runs {
1022 for glyph in &run.glyphs {
1023 if let Some(limit) = truncate_at_visual_index
1024 && emitted >= limit
1025 {
1026 break 'emit;
1027 }
1028 rasterize_glyph_quad(
1029 service,
1030 glyph,
1031 run,
1032 pen_x,
1033 baseline,
1034 text_color,
1035 raster_scale,
1036 &mut quads,
1037 &mut keys,
1038 );
1039 pen_x += glyph.x_advance;
1040 emitted += 1;
1041 }
1042 }
1043
1044 if let Some(ref e_run) = ellipsis_run {
1045 for glyph in &e_run.glyphs {
1046 rasterize_glyph_quad(
1047 service,
1048 glyph,
1049 e_run,
1050 pen_x,
1051 baseline,
1052 text_color,
1053 raster_scale,
1054 &mut quads,
1055 &mut keys,
1056 );
1057 pen_x += glyph.x_advance;
1058 }
1059 }
1060
1061 SingleLineResult {
1062 width: final_width,
1063 height: line_height,
1064 baseline,
1065 underline_offset: metrics.underline_offset,
1066 underline_thickness: metrics.stroke_size,
1067 glyphs: quads,
1068 glyph_keys: keys,
1069 spans: Vec::new(),
1070 }
1071 }
1072
1073 pub fn layout_paragraph(
1087 &mut self,
1088 service: &mut TextFontService,
1089 text: &str,
1090 format: &TextFormat,
1091 max_width: f32,
1092 max_lines: Option<usize>,
1093 raster_scale: f32,
1094 ) -> ParagraphResult {
1095 let empty = ParagraphResult {
1096 width: 0.0,
1097 height: 0.0,
1098 baseline_first: 0.0,
1099 line_count: 0,
1100 line_height: 0.0,
1101 underline_offset: 0.0,
1102 underline_thickness: 0.0,
1103 glyphs: Vec::new(),
1104 glyph_keys: Vec::new(),
1105 spans: Vec::new(),
1106 };
1107
1108 if text.is_empty() || max_width <= 0.0 {
1109 return empty;
1110 }
1111
1112 let font_point_size = format.font_size.map(|s| s as u32);
1113 let resolved = match resolve_font(
1114 &service.font_registry,
1115 format.font_family.as_deref(),
1116 format.font_weight,
1117 format.font_bold,
1118 format.font_italic,
1119 font_point_size,
1120 service.scale_factor,
1121 1.0, ) {
1123 Some(r) => r,
1124 None => return empty,
1125 };
1126
1127 let metrics = match font_metrics_px(&service.font_registry, &resolved) {
1128 Some(m) => m,
1129 None => return empty,
1130 };
1131
1132 let features = to_harfrust_features(&format.features);
1133 let runs: Vec<_> = bidi_runs(text)
1134 .into_iter()
1135 .filter_map(|br| {
1136 let slice = text.get(br.byte_range.clone())?;
1137 shape_text_with_fallback(
1138 &service.font_registry,
1139 &resolved,
1140 slice,
1141 br.byte_range.start,
1142 br.direction,
1143 &features,
1144 )
1145 })
1146 .collect();
1147
1148 if runs.is_empty() {
1149 return empty;
1150 }
1151
1152 let hyphenator = format.hyphenation.and_then(|h| {
1153 shape_text(&service.font_registry, &resolved, "-", 0)
1154 .and_then(|r| r.glyphs.into_iter().next())
1155 .map(|glyph| Hyphenator {
1156 glyph,
1157 language: h.language,
1158 })
1159 });
1160 let lines = break_into_lines(
1161 runs,
1162 text,
1163 max_width,
1164 Alignment::Left,
1165 0.0,
1166 &metrics,
1167 hyphenator,
1168 );
1169
1170 let line_count = match max_lines {
1171 Some(n) => lines.len().min(n),
1172 None => lines.len(),
1173 };
1174
1175 let text_color = format.color.unwrap_or(self.text_color);
1176 let mut quads: Vec<GlyphQuad> = Vec::new();
1177 let mut keys: Vec<crate::atlas::cache::GlyphCacheKey> = Vec::new();
1178 let mut y_top = 0.0f32;
1179 let mut max_line_width = 0.0f32;
1180 let baseline_first = metrics.ascent;
1181
1182 for line in lines.iter().take(line_count) {
1183 if line.width > max_line_width {
1184 max_line_width = line.width;
1185 }
1186 let baseline_y = y_top + metrics.ascent;
1187 for run in &line.runs {
1188 let mut pen_x = run.x;
1189 let run_copy = run.shaped_run.clone();
1190 for glyph in &run_copy.glyphs {
1191 rasterize_glyph_quad(
1192 service,
1193 glyph,
1194 &run_copy,
1195 pen_x,
1196 baseline_y,
1197 text_color,
1198 raster_scale,
1199 &mut quads,
1200 &mut keys,
1201 );
1202 pen_x += glyph.x_advance;
1203 }
1204 }
1205 y_top += metrics.ascent + metrics.descent + metrics.leading;
1206 }
1207
1208 let line_height = metrics.ascent + metrics.descent + metrics.leading;
1209 ParagraphResult {
1210 width: max_line_width,
1211 height: y_top,
1212 baseline_first,
1213 line_count,
1214 line_height,
1215 underline_offset: metrics.underline_offset,
1216 underline_thickness: metrics.stroke_size,
1217 glyphs: quads,
1218 glyph_keys: keys,
1219 spans: Vec::new(),
1220 }
1221 }
1222
1223 pub fn layout_single_line_markup(
1229 &mut self,
1230 service: &mut TextFontService,
1231 markup: &InlineMarkup,
1232 format: &TextFormat,
1233 max_width: Option<f32>,
1234 raster_scale: f32,
1235 ) -> SingleLineResult {
1236 if markup.spans.is_empty() {
1237 return SingleLineResult {
1238 width: 0.0,
1239 height: 0.0,
1240 baseline: 0.0,
1241 underline_offset: 0.0,
1242 underline_thickness: 0.0,
1243 glyphs: Vec::new(),
1244 glyph_keys: Vec::new(),
1245 spans: Vec::new(),
1246 };
1247 }
1248
1249 let per_span: Vec<(SingleLineResult, &crate::layout::inline_markup::InlineSpan)> = markup
1250 .spans
1251 .iter()
1252 .map(|sp| {
1253 let fmt = merge_format(format, sp.attrs);
1254 let r = if sp.text.is_empty() {
1255 SingleLineResult {
1256 width: 0.0,
1257 height: 0.0,
1258 baseline: 0.0,
1259 underline_offset: 0.0,
1260 underline_thickness: 0.0,
1261 glyphs: Vec::new(),
1262 glyph_keys: Vec::new(),
1263 spans: Vec::new(),
1264 }
1265 } else {
1266 self.layout_single_line(service, &sp.text, &fmt, None, raster_scale)
1267 };
1268 (r, sp)
1269 })
1270 .collect();
1271
1272 let total_width: f32 = per_span.iter().map(|(r, _)| r.width).sum();
1273 let line_height = per_span
1274 .iter()
1275 .map(|(r, _)| r.height)
1276 .fold(0.0f32, f32::max);
1277 let baseline = per_span
1278 .iter()
1279 .map(|(r, _)| r.baseline)
1280 .fold(0.0f32, f32::max);
1281 let (underline_offset, underline_thickness) = per_span
1285 .iter()
1286 .map(|(r, _)| (r.underline_offset, r.underline_thickness))
1287 .find(|(_, t)| *t > 0.0)
1288 .unwrap_or((0.0, 0.0));
1289
1290 let truncate = match max_width {
1291 Some(mw) if total_width > mw => Some(mw),
1292 _ => None,
1293 };
1294
1295 let mut glyphs: Vec<GlyphQuad> = Vec::new();
1296 let mut all_keys: Vec<crate::atlas::cache::GlyphCacheKey> = Vec::new();
1297 let mut spans_out: Vec<LaidOutSpan> = Vec::new();
1298 let mut pen_x: f32 = 0.0;
1299 let effective_width = truncate.unwrap_or(total_width);
1300
1301 for (r, sp) in &per_span {
1302 let remaining = (effective_width - pen_x).max(0.0);
1303 let span_visible_width = r.width.min(remaining);
1304 if span_visible_width <= 0.0 && r.width > 0.0 {
1305 spans_out.push(LaidOutSpan {
1306 kind: if let Some(url) = sp.link_url.clone() {
1307 LaidOutSpanKind::Link { url }
1308 } else {
1309 LaidOutSpanKind::Text
1310 },
1311 line_index: 0,
1312 rect: [pen_x, 0.0, 0.0, line_height],
1313 byte_range: sp.byte_range.clone(),
1314 });
1315 continue;
1316 }
1317
1318 for (gi, g) in r.glyphs.iter().enumerate() {
1319 let g_right = pen_x + g.screen[0] + g.screen[2];
1320 if g_right > effective_width + 0.5 {
1321 break;
1322 }
1323 let mut gq = g.clone();
1324 gq.screen[0] += pen_x;
1325 glyphs.push(gq);
1326 if let Some(k) = r.glyph_keys.get(gi) {
1327 all_keys.push(*k);
1328 }
1329 }
1330
1331 spans_out.push(LaidOutSpan {
1332 kind: if let Some(url) = sp.link_url.clone() {
1333 LaidOutSpanKind::Link { url }
1334 } else {
1335 LaidOutSpanKind::Text
1336 },
1337 line_index: 0,
1338 rect: [pen_x, 0.0, span_visible_width, line_height],
1339 byte_range: sp.byte_range.clone(),
1340 });
1341
1342 pen_x += r.width;
1343 if truncate.is_some() && pen_x >= effective_width {
1344 break;
1345 }
1346 }
1347
1348 SingleLineResult {
1349 width: effective_width,
1350 height: line_height,
1351 baseline,
1352 underline_offset,
1353 underline_thickness,
1354 glyphs,
1355 glyph_keys: all_keys,
1356 spans: spans_out,
1357 }
1358 }
1359
1360 pub fn layout_paragraph_markup(
1365 &mut self,
1366 service: &mut TextFontService,
1367 markup: &InlineMarkup,
1368 format: &TextFormat,
1369 max_width: f32,
1370 max_lines: Option<usize>,
1371 raster_scale: f32,
1372 ) -> ParagraphResult {
1373 let empty = ParagraphResult {
1374 width: 0.0,
1375 height: 0.0,
1376 baseline_first: 0.0,
1377 line_count: 0,
1378 line_height: 0.0,
1379 underline_offset: 0.0,
1380 underline_thickness: 0.0,
1381 glyphs: Vec::new(),
1382 glyph_keys: Vec::new(),
1383 spans: Vec::new(),
1384 };
1385
1386 if markup.spans.is_empty() || max_width <= 0.0 {
1387 return empty;
1388 }
1389
1390 let mut flat = String::new();
1391 let mut span_flat_offsets: Vec<usize> = Vec::with_capacity(markup.spans.len());
1392 for sp in &markup.spans {
1393 span_flat_offsets.push(flat.len());
1394 flat.push_str(&sp.text);
1395 }
1396 if flat.is_empty() {
1397 return empty;
1398 }
1399
1400 let base_point_size = format.font_size.map(|s| s as u32);
1401 let base_resolved = match resolve_font(
1402 &service.font_registry,
1403 format.font_family.as_deref(),
1404 format.font_weight,
1405 format.font_bold,
1406 format.font_italic,
1407 base_point_size,
1408 service.scale_factor,
1409 1.0, ) {
1411 Some(r) => r,
1412 None => return empty,
1413 };
1414 let metrics = match font_metrics_px(&service.font_registry, &base_resolved) {
1415 Some(m) => m,
1416 None => return empty,
1417 };
1418
1419 let mut all_runs: Vec<ShapedRun> = Vec::new();
1420 for (span_idx, sp) in markup.spans.iter().enumerate() {
1421 if sp.text.is_empty() {
1422 continue;
1423 }
1424 let fmt = merge_format(format, sp.attrs);
1425 let span_point_size = fmt.font_size.map(|s| s as u32);
1426 let Some(resolved) = resolve_font(
1427 &service.font_registry,
1428 fmt.font_family.as_deref(),
1429 fmt.font_weight,
1430 fmt.font_bold,
1431 fmt.font_italic,
1432 span_point_size,
1433 service.scale_factor,
1434 1.0, ) else {
1436 continue;
1437 };
1438
1439 let flat_start = span_flat_offsets[span_idx];
1440 let features = to_harfrust_features(&fmt.features);
1441 for br in bidi_runs(&sp.text) {
1442 let slice = match sp.text.get(br.byte_range.clone()) {
1443 Some(s) => s,
1444 None => continue,
1445 };
1446 let Some(mut run) = shape_text_with_fallback(
1447 &service.font_registry,
1448 &resolved,
1449 slice,
1450 flat_start + br.byte_range.start,
1451 br.direction,
1452 &features,
1453 ) else {
1454 continue;
1455 };
1456 if let Some(url) = sp.link_url.as_ref() {
1457 run.is_link = true;
1458 run.anchor_href = Some(url.clone());
1459 }
1460 all_runs.push(run);
1461 }
1462 }
1463
1464 if all_runs.is_empty() {
1465 return empty;
1466 }
1467
1468 let hyphenator = format.hyphenation.and_then(|h| {
1469 shape_text(&service.font_registry, &base_resolved, "-", 0)
1470 .and_then(|r| r.glyphs.into_iter().next())
1471 .map(|glyph| Hyphenator {
1472 glyph,
1473 language: h.language,
1474 })
1475 });
1476 let lines = break_into_lines(
1477 all_runs,
1478 &flat,
1479 max_width,
1480 Alignment::Left,
1481 0.0,
1482 &metrics,
1483 hyphenator,
1484 );
1485
1486 let line_count = match max_lines {
1487 Some(n) => lines.len().min(n),
1488 None => lines.len(),
1489 };
1490
1491 let text_color = format.color.unwrap_or(self.text_color);
1492 let mut glyphs_out: Vec<GlyphQuad> = Vec::new();
1493 let mut keys_out: Vec<crate::atlas::cache::GlyphCacheKey> = Vec::new();
1494 let mut spans_out: Vec<LaidOutSpan> = Vec::new();
1495 let line_height = metrics.ascent + metrics.descent + metrics.leading;
1496 let mut y_top: f32 = 0.0;
1497 let mut max_line_width: f32 = 0.0;
1498 let baseline_first = metrics.ascent;
1499
1500 for (line_idx, line) in lines.iter().take(line_count).enumerate() {
1501 if line.width > max_line_width {
1502 max_line_width = line.width;
1503 }
1504 let baseline_y = y_top + metrics.ascent;
1505
1506 for pr in &line.runs {
1507 let run_copy = pr.shaped_run.clone();
1508 let mut pen_x = pr.x;
1509 for glyph in &run_copy.glyphs {
1510 rasterize_glyph_quad(
1511 service,
1512 glyph,
1513 &run_copy,
1514 pen_x,
1515 baseline_y,
1516 text_color,
1517 raster_scale,
1518 &mut glyphs_out,
1519 &mut keys_out,
1520 );
1521 pen_x += glyph.x_advance;
1522 }
1523
1524 if pr.decorations.is_link
1525 && let Some(url) = pr.decorations.anchor_href.clone()
1526 {
1527 let width = pr.shaped_run.advance_width;
1528 spans_out.push(LaidOutSpan {
1529 kind: LaidOutSpanKind::Link { url },
1530 line_index: line_idx,
1531 rect: [pr.x, y_top, width, line_height],
1532 byte_range: pr.shaped_run.text_range.clone(),
1533 });
1534 }
1535 }
1536
1537 y_top += line_height;
1538 }
1539
1540 ParagraphResult {
1541 width: max_line_width,
1542 height: y_top,
1543 baseline_first,
1544 line_count,
1545 line_height,
1546 underline_offset: metrics.underline_offset,
1547 underline_thickness: metrics.stroke_size,
1548 glyphs: glyphs_out,
1549 glyph_keys: keys_out,
1550 spans: spans_out,
1551 }
1552 }
1553
1554 pub fn hit_test(&self, x: f32, y: f32) -> Option<HitTestResult> {
1561 crate::render::hit_test::hit_test(
1562 &self.flow_layout,
1563 self.scroll_offset,
1564 x / self.zoom,
1565 y / self.zoom,
1566 )
1567 }
1568
1569 pub fn character_geometry(
1578 &self,
1579 block_id: usize,
1580 char_start: usize,
1581 char_end: usize,
1582 ) -> Vec<CharacterGeometry> {
1583 if char_start >= char_end {
1584 return Vec::new();
1585 }
1586 let block = match self.flow_layout.blocks.get(&block_id) {
1587 Some(b) => b,
1588 None => return Vec::new(),
1589 };
1590
1591 let mut absolute: Vec<(usize, f32)> = Vec::with_capacity(char_end - char_start);
1592 for line in &block.lines {
1593 if line.char_range.end <= char_start || line.char_range.start >= char_end {
1594 continue;
1595 }
1596 let local_start = char_start.max(line.char_range.start);
1597 let local_end = char_end.min(line.char_range.end);
1598 for c in local_start..local_end {
1599 let x = line.x_for_offset(c);
1600 absolute.push((c, x));
1601 }
1602 if local_end == char_end {
1603 let x_end = line.x_for_offset(local_end);
1604 absolute.push((local_end, x_end));
1605 }
1606 }
1607
1608 if absolute.is_empty() {
1609 return Vec::new();
1610 }
1611
1612 absolute.sort_by_key(|(c, _)| *c);
1613
1614 let base_x = absolute.first().map(|(_, x)| *x).unwrap_or(0.0);
1615 let mut out: Vec<CharacterGeometry> = Vec::with_capacity(absolute.len());
1616 for window in absolute.windows(2) {
1617 let (c, x) = window[0];
1618 let (_, x_next) = window[1];
1619 if c >= char_end {
1620 break;
1621 }
1622 out.push(CharacterGeometry {
1623 position: x - base_x,
1624 width: (x_next - x).max(0.0),
1625 });
1626 }
1627 out
1628 }
1629
1630 pub fn caret_rect(&self, position: usize, affinity: crate::types::CursorAffinity) -> [f32; 4] {
1640 let mut rect = crate::render::hit_test::caret_rect(
1641 &self.flow_layout,
1642 self.scroll_offset,
1643 position,
1644 affinity,
1645 );
1646 rect[0] *= self.zoom;
1647 rect[1] *= self.zoom;
1648 rect[2] *= self.zoom;
1649 rect[3] *= self.zoom;
1650 rect
1651 }
1652
1653 pub fn set_cursor(&mut self, cursor: &CursorDisplay) {
1657 self.cursors = vec![CursorDisplay {
1658 position: cursor.position,
1659 anchor: cursor.anchor,
1660 affinity: cursor.affinity,
1661 visible: cursor.visible,
1662 selected_cells: cursor.selected_cells.clone(),
1663 }];
1664 }
1665
1666 pub fn set_cursors(&mut self, cursors: &[CursorDisplay]) {
1670 self.cursors = cursors
1671 .iter()
1672 .map(|c| CursorDisplay {
1673 position: c.position,
1674 anchor: c.anchor,
1675 affinity: c.affinity,
1676 visible: c.visible,
1677 selected_cells: c.selected_cells.clone(),
1678 })
1679 .collect();
1680 }
1681
1682 pub fn set_selection_color(&mut self, color: [f32; 4]) {
1685 self.selection_color = color;
1686 }
1687
1688 pub fn set_cursor_color(&mut self, color: [f32; 4]) {
1690 self.cursor_color = color;
1691 }
1692
1693 pub fn set_text_color(&mut self, color: [f32; 4]) {
1696 self.text_color = color;
1697 }
1698
1699 pub fn text_color(&self) -> [f32; 4] {
1701 self.text_color
1702 }
1703
1704 pub fn set_code_block_background(&mut self, color: [f32; 4]) {
1711 self.code_block_background = color;
1712 }
1713
1714 pub fn code_block_background(&self) -> [f32; 4] {
1716 self.code_block_background
1717 }
1718
1719 pub fn set_hyphenate_justified(&mut self, enabled: bool) {
1724 self.hyphenate_justified = enabled;
1725 }
1726
1727 pub fn hyphenate_justified(&self) -> bool {
1729 self.hyphenate_justified
1730 }
1731
1732 pub fn set_code_block_foreground(&mut self, color: Option<[f32; 4]>) {
1737 self.code_block_foreground = color;
1738 }
1739
1740 pub fn code_block_foreground(&self) -> Option<[f32; 4]> {
1742 self.code_block_foreground
1743 }
1744
1745 pub fn set_echo_char(&mut self, echo: Option<char>) {
1760 self.echo_char = echo;
1761 }
1762
1763 pub fn echo_char(&self) -> Option<char> {
1765 self.echo_char
1766 }
1767
1768 pub fn block_visual_info(&self, block_id: usize) -> Option<BlockVisualInfo> {
1773 let block = self.flow_layout.blocks.get(&block_id)?;
1774 Some(BlockVisualInfo {
1775 block_id,
1776 y: block.y,
1777 height: block.height,
1778 })
1779 }
1780
1781 pub fn is_block_in_table(&self, block_id: usize) -> bool {
1783 self.flow_layout.tables.values().any(|table| {
1784 table
1785 .cell_layouts
1786 .iter()
1787 .any(|cell| cell.blocks.iter().any(|b| b.block_id == block_id))
1788 })
1789 }
1790
1791 pub fn scroll_to_position(&mut self, position: usize) -> f32 {
1796 let rect = crate::render::hit_test::caret_rect(
1797 &self.flow_layout,
1798 self.scroll_offset,
1799 position,
1800 crate::types::CursorAffinity::Downstream,
1801 );
1802 let target_y = rect[1] + self.scroll_offset - self.viewport_height / (3.0 * self.zoom);
1803 self.scroll_offset = target_y.max(0.0);
1804 self.scroll_offset
1805 }
1806
1807 pub fn ensure_caret_visible(&mut self) -> Option<f32> {
1811 if self.cursors.is_empty() {
1812 return None;
1813 }
1814 let pos = self.cursors[0].position;
1815 let affinity = self.cursors[0].affinity;
1816 let rect = crate::render::hit_test::caret_rect(
1817 &self.flow_layout,
1818 self.scroll_offset,
1819 pos,
1820 affinity,
1821 );
1822 let caret_screen_y = rect[1];
1823 let caret_screen_bottom = caret_screen_y + rect[3];
1824 let effective_vh = self.viewport_height / self.zoom;
1825 let margin = 10.0 / self.zoom;
1826 let old_offset = self.scroll_offset;
1827
1828 if caret_screen_y < 0.0 {
1829 self.scroll_offset += caret_screen_y - margin;
1830 self.scroll_offset = self.scroll_offset.max(0.0);
1831 } else if caret_screen_bottom > effective_vh {
1832 self.scroll_offset += caret_screen_bottom - effective_vh + margin;
1833 }
1834
1835 if (self.scroll_offset - old_offset).abs() > 0.001 {
1836 Some(self.scroll_offset)
1837 } else {
1838 None
1839 }
1840 }
1841}
1842
1843impl Default for DocumentFlow {
1844 fn default() -> Self {
1845 Self::new()
1846 }
1847}
1848
1849#[cfg(feature = "text-document")]
1850enum FlowItemKind {
1851 Block(BlockLayoutParams),
1852 Table(TableLayoutParams),
1853 Frame(FrameLayoutParams),
1854}
1855
1856#[allow(clippy::too_many_arguments)]
1869fn rasterize_glyph_quad(
1870 service: &mut TextFontService,
1871 glyph: &ShapedGlyph,
1872 run: &ShapedRun,
1873 pen_x: f32,
1874 baseline: f32,
1875 text_color: [f32; 4],
1876 raster_scale: f32,
1877 quads: &mut Vec<GlyphQuad>,
1878 glyph_keys: &mut Vec<crate::atlas::cache::GlyphCacheKey>,
1879) {
1880 use crate::atlas::cache::GlyphCacheKey;
1881 use crate::atlas::rasterizer::rasterize_glyph;
1882
1883 if glyph.glyph_id == 0 {
1884 return;
1885 }
1886
1887 let entry = match service.font_registry.get(glyph.font_face_id) {
1888 Some(e) => e,
1889 None => return,
1890 };
1891
1892 let raster_scale = if raster_scale > 0.0 {
1893 raster_scale
1894 } else {
1895 1.0
1896 };
1897 let hinted = raster_scale == 1.0;
1898 let sf = service.scale_factor.max(f32::MIN_POSITIVE);
1899 let inv_total = 1.0 / (sf * raster_scale);
1900 let physical_size_px = run.size_px * sf * raster_scale;
1901 let cache_key = GlyphCacheKey::with_weight(
1902 glyph.font_face_id,
1903 glyph.glyph_id,
1904 physical_size_px,
1905 run.weight as u32,
1906 hinted,
1907 );
1908
1909 if service.glyph_cache.peek(&cache_key).is_none()
1910 && let Some(image) = rasterize_glyph(
1911 &mut service.scale_context,
1912 entry.bytes(),
1913 entry.face_index,
1914 entry.swash_cache_key,
1915 glyph.glyph_id,
1916 physical_size_px,
1917 run.weight as u32,
1918 hinted,
1919 )
1920 && image.width > 0
1921 && image.height > 0
1922 {
1923 let (alloc, evicted) = crate::atlas::allocate_or_evict(
1924 &mut service.atlas,
1925 &mut service.glyph_cache,
1926 image.width,
1927 image.height,
1928 );
1929 if evicted {
1930 service.eviction_epoch = service.eviction_epoch.wrapping_add(1);
1931 }
1932 if let Some(alloc) = alloc {
1933 let rect = alloc.rectangle;
1934 let atlas_x = rect.min.x as u32;
1935 let atlas_y = rect.min.y as u32;
1936 if image.is_color {
1937 service
1938 .atlas
1939 .blit_rgba(atlas_x, atlas_y, image.width, image.height, &image.data);
1940 } else {
1941 service
1942 .atlas
1943 .blit_mask(atlas_x, atlas_y, image.width, image.height, &image.data);
1944 }
1945 service.glyph_cache.insert(
1946 cache_key,
1947 crate::atlas::cache::CachedGlyph {
1948 alloc_id: alloc.id,
1949 atlas_x,
1950 atlas_y,
1951 width: image.width,
1952 height: image.height,
1953 placement_left: image.placement_left,
1954 placement_top: image.placement_top,
1955 is_color: image.is_color,
1956 last_used: 0,
1957 },
1958 );
1959 }
1960 }
1961
1962 if let Some(cached) = service.glyph_cache.get(&cache_key) {
1963 let logical_w = cached.width as f32 * inv_total;
1964 let logical_h = cached.height as f32 * inv_total;
1965 let logical_left = cached.placement_left as f32 * inv_total;
1966 let logical_top = cached.placement_top as f32 * inv_total;
1967 let screen_x = pen_x + glyph.x_offset + logical_left;
1968 let screen_y = baseline - glyph.y_offset - logical_top;
1969 let color = if cached.is_color {
1970 [1.0, 1.0, 1.0, 1.0]
1971 } else {
1972 text_color
1973 };
1974 quads.push(GlyphQuad {
1975 screen: [screen_x, screen_y, logical_w, logical_h],
1976 atlas: [
1977 cached.atlas_x as f32,
1978 cached.atlas_y as f32,
1979 cached.width as f32,
1980 cached.height as f32,
1981 ],
1982 color,
1983 is_color: cached.is_color,
1984 });
1985 glyph_keys.push(cache_key);
1986 }
1987}
1988
1989fn apply_zoom(frame: &mut RenderFrame, zoom: f32) {
1991 if (zoom - 1.0).abs() <= f32::EPSILON {
1992 return;
1993 }
1994 for q in &mut frame.glyphs {
1995 q.screen[0] *= zoom;
1996 q.screen[1] *= zoom;
1997 q.screen[2] *= zoom;
1998 q.screen[3] *= zoom;
1999 }
2000 for q in &mut frame.images {
2001 q.screen[0] *= zoom;
2002 q.screen[1] *= zoom;
2003 q.screen[2] *= zoom;
2004 q.screen[3] *= zoom;
2005 }
2006 apply_zoom_decorations(&mut frame.decorations, zoom);
2007}
2008
2009fn apply_zoom_decorations(decorations: &mut [DecorationRect], zoom: f32) {
2011 if (zoom - 1.0).abs() <= f32::EPSILON {
2012 return;
2013 }
2014 for d in decorations.iter_mut() {
2015 d.rect[0] *= zoom;
2016 d.rect[1] *= zoom;
2017 d.rect[2] *= zoom;
2018 d.rect[3] *= zoom;
2019 }
2020}
2021
2022fn merge_format(base: &TextFormat, attrs: InlineAttrs) -> TextFormat {
2025 let mut fmt = base.clone();
2026 if attrs.is_bold() {
2027 fmt.font_bold = Some(true);
2028 if let Some(w) = fmt.font_weight
2029 && w < 600
2030 {
2031 fmt.font_weight = Some(700);
2032 } else if fmt.font_weight.is_none() {
2033 fmt.font_weight = Some(700);
2034 }
2035 }
2036 if attrs.is_italic() {
2037 fmt.font_italic = Some(true);
2038 }
2039 fmt
2040}
2041
2042#[cfg(test)]
2043mod tests {
2044 use super::*;
2045 use crate::layout::block::{BlockLayoutParams, FragmentParams};
2046 use crate::layout::paragraph::Alignment;
2047 use crate::types::{UnderlineStyle, VerticalAlignment};
2048
2049 const NOTO_SANS: &[u8] = include_bytes!("../test-fonts/NotoSans-Variable.ttf");
2050
2051 fn service() -> TextFontService {
2052 let mut s = TextFontService::new_without_system_fonts();
2054 let face = s.register_font(NOTO_SANS);
2055 s.set_default_font(face, 16.0);
2056 s
2057 }
2058
2059 fn block(id: usize, text: &str) -> BlockLayoutParams {
2060 BlockLayoutParams {
2061 block_id: id,
2062 position: 0,
2063 text: text.to_string(),
2064 fragments: vec![FragmentParams {
2065 text: text.to_string(),
2066 offset: 0,
2067 length: text.len(),
2068 font_family: None,
2069 font_weight: None,
2070 font_bold: None,
2071 font_italic: None,
2072 font_point_size: None,
2073 underline_style: UnderlineStyle::None,
2074 overline: false,
2075 strikeout: false,
2076 is_link: false,
2077 letter_spacing: 0.0,
2078 word_spacing: 0.0,
2079 foreground_color: None,
2080 underline_color: None,
2081 background_color: None,
2082 anchor_href: None,
2083 tooltip: None,
2084 vertical_alignment: VerticalAlignment::Normal,
2085 image_name: None,
2086 image_width: 0.0,
2087 image_height: 0.0,
2088 features: Vec::new(),
2089 }],
2090 alignment: Alignment::Left,
2091 top_margin: 0.0,
2092 bottom_margin: 0.0,
2093 left_margin: 0.0,
2094 right_margin: 0.0,
2095 text_indent: 0.0,
2096 list_marker: String::new(),
2097 list_indent: 0.0,
2098 tab_positions: vec![],
2099 line_height_multiplier: None,
2100 non_breakable_lines: false,
2101 hyphenation: None,
2102 checkbox: None,
2103 background_color: None,
2104 }
2105 }
2106
2107 #[test]
2108 fn relayout_block_returns_no_layout_when_never_laid_out() {
2109 let svc = service();
2110 let mut flow = DocumentFlow::new();
2111 flow.set_viewport(400.0, 200.0);
2112 let err = flow.relayout_block(&svc, &block(1, "Hello")).unwrap_err();
2113 assert_eq!(err, RelayoutError::NoLayout);
2114 }
2115
2116 #[test]
2117 fn relayout_block_returns_scale_dirty_after_scale_factor_change() {
2118 let mut svc = service();
2119 let mut flow = DocumentFlow::new();
2120 flow.set_viewport(400.0, 200.0);
2121 flow.layout_blocks(&svc, vec![block(1, "Hello")]);
2122 assert!(flow.has_layout());
2123
2124 svc.set_scale_factor(2.0);
2126 assert!(flow.layout_dirty_for_scale(&svc));
2127
2128 let err = flow
2129 .relayout_block(&svc, &block(1, "Hello world"))
2130 .unwrap_err();
2131 assert_eq!(err, RelayoutError::ScaleDirty);
2132 }
2133
2134 #[test]
2135 fn relayout_block_succeeds_after_fresh_layout_post_scale_change() {
2136 let mut svc = service();
2137 let mut flow = DocumentFlow::new();
2138 flow.set_viewport(400.0, 200.0);
2139 flow.layout_blocks(&svc, vec![block(1, "Hello")]);
2140
2141 svc.set_scale_factor(2.0);
2142 flow.layout_blocks(&svc, vec![block(1, "Hello")]);
2145 assert!(!flow.layout_dirty_for_scale(&svc));
2146
2147 flow.relayout_block(&svc, &block(1, "Hello world"))
2149 .expect("relayout_block must succeed after a fresh post-scale layout");
2150 }
2151}