1use markup5ever::{QualName, expanded_name, local_name, ns};
4use pango::IsAttribute;
5use pango::prelude::FontExt;
6use std::cell::RefCell;
7use std::convert::TryFrom;
8use std::rc::Rc;
9
10use crate::document::{AcquiredNodes, NodeId};
11use crate::drawing_ctx::{DrawingCtx, FontOptions, Viewport, create_pango_context};
12use crate::element::{DrawResult, ElementData, ElementTrait, set_attribute};
13use crate::error::*;
14use crate::layout::{self, FontProperties, Layer, LayerKind, StackingContext, Stroke, TextSpan};
15use crate::length::*;
16use crate::node::{CascadedValues, Node, NodeBorrow};
17use crate::paint_server::PaintSource;
18use crate::parsers::{CommaSeparatedList, Parse, ParseValue};
19use crate::properties::{
20 ComputedValues, Direction, DominantBaseline, FontStretch, FontStyle, FontVariant, FontWeight,
21 PaintOrder, TextAnchor, TextRendering, UnicodeBidi, WritingMode, XmlLang, XmlSpace,
22};
23use crate::rect::Rect;
24use crate::rsvg_log;
25use crate::session::Session;
26use crate::space::{NormalizeDefault, XmlSpaceNormalize, xml_space_normalize};
27use crate::xml::Attributes;
28
29struct LayoutContext {
31 writing_mode: WritingMode,
33
34 font_options: FontOptions,
36
37 viewport: Viewport,
39
40 session: Session,
42}
43
44struct Chunk {
58 values: Rc<ComputedValues>,
59 x: Option<f64>,
60 y: Option<f64>,
61 spans: Vec<Span>,
62}
63
64struct MeasuredChunk {
65 values: Rc<ComputedValues>,
66 x: Option<f64>,
67 y: Option<f64>,
68 dx: f64,
69 dy: f64,
70 spans: Vec<MeasuredSpan>,
71}
72
73struct PositionedChunk {
74 next_chunk_x: f64,
75 next_chunk_y: f64,
76 spans: Vec<PositionedSpan>,
77}
78
79struct Span {
80 values: Rc<ComputedValues>,
81 text: String,
82 dx: f64,
83 dy: f64,
84 _depth: usize,
85 link_target: Option<String>,
86 span_element_name: Rc<String>,
87}
88
89struct MeasuredSpan {
90 values: Rc<ComputedValues>,
91 layout: pango::Layout,
92 layout_size: (f64, f64),
93 advance: (f64, f64),
94 dx: f64,
95 dy: f64,
96 link_target: Option<String>,
97 span_element_name: Rc<String>,
98}
99
100struct PositionedSpan {
101 layout: pango::Layout,
102 values: Rc<ComputedValues>,
103 rendered_position: (f64, f64),
104 next_span_position: (f64, f64),
105 link_target: Option<String>,
106 span_element_name: Rc<String>,
107}
108
109struct LayoutSpan {
115 layout: pango::Layout,
116 gravity: pango::Gravity,
117 extents: Option<Rect>,
118 is_visible: bool,
119 x: f64,
120 y: f64,
121 paint_order: PaintOrder,
122 stroke: Stroke,
123 stroke_paint: Rc<PaintSource>,
124 fill_paint: Rc<PaintSource>,
125 text_rendering: TextRendering,
126 link_target: Option<String>,
127 values: Rc<ComputedValues>,
128}
129
130impl Chunk {
131 fn new(values: &ComputedValues, x: Option<f64>, y: Option<f64>) -> Chunk {
132 Chunk {
133 values: Rc::new(values.clone()),
134 x,
135 y,
136 spans: Vec::new(),
137 }
138 }
139}
140
141impl MeasuredChunk {
142 fn from_chunk(layout_context: &LayoutContext, chunk: &Chunk) -> MeasuredChunk {
143 let mut measured_spans: Vec<MeasuredSpan> = chunk
144 .spans
145 .iter()
146 .filter_map(|span| MeasuredSpan::from_span(layout_context, span))
147 .collect();
148
149 let (chunk_dx, chunk_dy) = if let Some(first) = measured_spans.first_mut() {
154 let dx = first.dx;
155 let dy = first.dy;
156 first.dx = 0.0;
157 first.dy = 0.0;
158 (dx, dy)
159 } else {
160 (0.0, 0.0)
161 };
162
163 MeasuredChunk {
164 values: chunk.values.clone(),
165 x: chunk.x,
166 y: chunk.y,
167 dx: chunk_dx,
168 dy: chunk_dy,
169 spans: measured_spans,
170 }
171 }
172}
173
174impl PositionedChunk {
175 fn from_measured(
176 layout_context: &LayoutContext,
177 measured: &MeasuredChunk,
178 chunk_x: f64,
179 chunk_y: f64,
180 ) -> PositionedChunk {
181 let chunk_direction = measured.values.direction();
182
183 let mut positioned = Vec::new();
186
187 let mut x = 0.0;
190 let mut y = 0.0;
191
192 let mut chunk_bounds: Option<Rect> = None;
193
194 for mspan in &measured.spans {
198 let params = NormalizeParams::new(&mspan.values, &layout_context.viewport);
199
200 let layout = mspan.layout.clone();
201 let layout_size = mspan.layout_size;
202 let values = mspan.values.clone();
203 let dx = mspan.dx;
204 let dy = mspan.dy;
205 let advance = mspan.advance;
206
207 let baseline_offset = compute_baseline_offset(&layout, &values, ¶ms);
208
209 let start_pos = match chunk_direction {
210 Direction::Ltr => (x, y),
211 Direction::Rtl => (x - advance.0, y),
212 };
213
214 let span_advance = match chunk_direction {
215 Direction::Ltr => (advance.0, advance.1),
216 Direction::Rtl => (-advance.0, advance.1),
217 };
218
219 let rendered_position = if layout_context.writing_mode.is_horizontal() {
220 (start_pos.0 + dx, start_pos.1 - baseline_offset + dy)
221 } else {
222 (start_pos.0 + baseline_offset + dx, start_pos.1 + dy)
223 };
224
225 let span_bounds =
226 Rect::from_size(layout_size.0, layout_size.1).translate(rendered_position);
227
228 if let Some(bounds) = chunk_bounds {
231 chunk_bounds = Some(bounds.union(&span_bounds));
232 } else {
233 chunk_bounds = Some(span_bounds);
234 }
235
236 x = x + span_advance.0 + dx;
237 y = y + span_advance.1 + dy;
238
239 let positioned_span = PositionedSpan {
240 layout,
241 values,
242 rendered_position,
243 next_span_position: (x, y),
244 link_target: mspan.link_target.clone(),
245 span_element_name: mspan.span_element_name.clone(),
246 };
247
248 positioned.push(positioned_span);
249 }
250
251 let anchor_offset = text_anchor_offset(
254 measured.values.text_anchor(),
255 chunk_direction,
256 layout_context.writing_mode,
257 chunk_bounds.unwrap_or_default(),
258 );
259
260 let mut next_chunk_x = chunk_x;
264 let mut next_chunk_y = chunk_y;
265
266 for pspan in &mut positioned {
267 pspan.rendered_position.0 += chunk_x + anchor_offset.0 + measured.dx;
271 pspan.rendered_position.1 += chunk_y + anchor_offset.1 + measured.dy;
272
273 next_chunk_x = chunk_x + pspan.next_span_position.0 + anchor_offset.0 + measured.dx;
274 next_chunk_y = chunk_y + pspan.next_span_position.1 + anchor_offset.1 + measured.dy;
275 }
276
277 PositionedChunk {
278 next_chunk_x,
279 next_chunk_y,
280 spans: positioned,
281 }
282 }
283}
284
285fn compute_baseline_offset(
286 layout: &pango::Layout,
287 values: &ComputedValues,
288 params: &NormalizeParams,
289) -> f64 {
290 let mut baseline = f64::from(layout.baseline()) / f64::from(pango::SCALE);
291 let dominant_baseline = values.dominant_baseline();
292
293 let mut layout_iter = layout.iter();
294 loop {
295 if let Some(layout_run) = layout_iter.run_readonly() {
296 let item = layout_run.item();
297 unsafe {
298 let analysis = (*item.as_ptr()).analysis;
299 if analysis.font.is_null() {
300 break;
301 }
302 }
303 let font = item.analysis().font();
304
305 let metrics = font.metrics(None);
306 let ascent = metrics.ascent();
307 let descent = metrics.descent();
308 let height = metrics.height();
309
310 match dominant_baseline {
311 DominantBaseline::Hanging => {
312 baseline -= f64::from(ascent - descent) / f64::from(pango::SCALE);
313 }
314 DominantBaseline::Middle => {
315 baseline -= f64::from(
318 metrics.strikethrough_position() + metrics.strikethrough_thickness() / 2,
319 ) / f64::from(pango::SCALE);
320 }
321 DominantBaseline::Central => {
322 baseline = 0.5 * f64::from(ascent + descent) / f64::from(pango::SCALE);
323 }
324 DominantBaseline::TextBeforeEdge | DominantBaseline::TextTop => {
325 baseline -= f64::from(2 * ascent - height) / f64::from(pango::SCALE);
328 }
329 DominantBaseline::TextAfterEdge | DominantBaseline::TextBottom => {
330 baseline += f64::from(descent) / f64::from(pango::SCALE);
331 }
332 DominantBaseline::Ideographic => {
333 baseline += f64::from(descent) / f64::from(pango::SCALE);
335 }
336 DominantBaseline::Mathematical => {
337 baseline = 0.5 * f64::from(ascent + descent) / f64::from(pango::SCALE);
339 }
340 _ => (),
341 }
342
343 break;
344 }
345
346 if !layout_iter.next_run() {
347 break;
348 }
349 }
350
351 let baseline_shift = values.baseline_shift().0.to_user(params);
352
353 baseline + baseline_shift
354}
355
356#[rustfmt::skip]
358fn text_anchor_offset(
359 anchor: TextAnchor,
360 direction: Direction,
361 writing_mode: WritingMode,
362 chunk_bounds: Rect,
363) -> (f64, f64) {
364 let (w, h) = (chunk_bounds.width(), chunk_bounds.height());
365
366 let x0 = chunk_bounds.x0;
367
368 if writing_mode.is_horizontal() {
369 match (anchor, direction) {
370 (TextAnchor::Start, Direction::Ltr) => (-x0, 0.0),
371 (TextAnchor::Start, Direction::Rtl) => (-x0 - w, 0.0),
372
373 (TextAnchor::Middle, Direction::Ltr) => (-x0 - w / 2.0, 0.0),
374 (TextAnchor::Middle, Direction::Rtl) => (-x0 - w / 2.0, 0.0),
375
376 (TextAnchor::End, Direction::Ltr) => (-x0 - w, 0.0),
377 (TextAnchor::End, Direction::Rtl) => (-x0, 0.0),
378 }
379 } else {
380 match anchor {
382 TextAnchor::Start => (0.0, 0.0),
383 TextAnchor::Middle => (0.0, -h / 2.0),
384 TextAnchor::End => (0.0, -h),
385 }
386 }
387}
388
389impl Span {
390 fn new(
391 text: &str,
392 span_element_name: Rc<String>,
393 values: Rc<ComputedValues>,
394 dx: f64,
395 dy: f64,
396 depth: usize,
397 link_target: Option<String>,
398 ) -> Span {
399 Span {
400 values,
401 text: text.to_string(),
402 dx,
403 dy,
404 _depth: depth,
405 link_target,
406 span_element_name,
407 }
408 }
409}
410
411struct PangoUnits(i32);
413
414impl PangoUnits {
415 fn from_pixels(v: f64) -> Option<Self> {
416 cast::i32(v * f64::from(pango::SCALE) + 0.5)
421 .ok()
422 .map(PangoUnits)
423 }
424}
425
426impl MeasuredSpan {
427 fn from_span(layout_context: &LayoutContext, span: &Span) -> Option<MeasuredSpan> {
428 let values = span.values.clone();
429
430 let params = NormalizeParams::new(&values, &layout_context.viewport);
431
432 let properties = FontProperties::new(&values, ¶ms);
433
434 let bidi_control = BidiControl::from_unicode_bidi_and_direction(
435 properties.unicode_bidi,
436 properties.direction,
437 );
438
439 let with_control_chars = wrap_with_direction_control_chars(&span.text, &bidi_control);
440
441 if let Some(layout) = create_pango_layout(layout_context, &properties, &with_control_chars)
442 {
443 let (w, h) = layout.size();
444
445 let w = f64::from(w) / f64::from(pango::SCALE);
446 let h = f64::from(h) / f64::from(pango::SCALE);
447
448 let advance = if layout_context.writing_mode.is_horizontal() {
449 (w, 0.0)
450 } else {
451 (0.0, w)
452 };
453
454 Some(MeasuredSpan {
455 values,
456 layout,
457 layout_size: (w, h),
458 advance,
459 dx: span.dx,
460 dy: span.dy,
461 link_target: span.link_target.clone(),
462 span_element_name: span.span_element_name.clone(),
463 })
464 } else {
465 None
466 }
467 }
468}
469
470fn gravity_is_vertical(gravity: pango::Gravity) -> bool {
472 matches!(gravity, pango::Gravity::East | pango::Gravity::West)
473}
474
475fn compute_text_box(
476 layout: &pango::Layout,
477 x: f64,
478 y: f64,
479 gravity: pango::Gravity,
480) -> Option<Rect> {
481 #![allow(clippy::many_single_char_names)]
482
483 let (ink, _) = layout.extents();
484 if ink.width() == 0 || ink.height() == 0 {
485 return None;
486 }
487
488 let ink_x = f64::from(ink.x());
489 let ink_y = f64::from(ink.y());
490 let ink_width = f64::from(ink.width());
491 let ink_height = f64::from(ink.height());
492 let pango_scale = f64::from(pango::SCALE);
493
494 let (x, y, w, h) = if gravity_is_vertical(gravity) {
495 (
496 x + (ink_x - ink_height) / pango_scale,
497 y + ink_y / pango_scale,
498 ink_height / pango_scale,
499 ink_width / pango_scale,
500 )
501 } else {
502 (
503 x + ink_x / pango_scale,
504 y + ink_y / pango_scale,
505 ink_width / pango_scale,
506 ink_height / pango_scale,
507 )
508 };
509
510 Some(Rect::new(x, y, x + w, y + h))
511}
512
513impl PositionedSpan {
514 fn layout(
515 &self,
516 layout_context: &LayoutContext,
517 acquired_nodes: &mut AcquiredNodes<'_>,
518 ) -> LayoutSpan {
519 let params = NormalizeParams::new(&self.values, &layout_context.viewport);
520
521 let layout = self.layout.clone();
522 let is_visible = self.values.is_visible();
523 let (x, y) = self.rendered_position;
524
525 let stroke = Stroke::new(&self.values, ¶ms);
526
527 let gravity = layout.context().gravity();
528
529 let extents = compute_text_box(&layout, x, y, gravity);
530
531 let stroke_paint = self.values.stroke().0.resolve(
532 acquired_nodes,
533 &self.span_element_name,
534 self.values.stroke_opacity().0,
535 self.values.color().0,
536 None,
537 None,
538 &layout_context.session,
539 );
540
541 let fill_paint = self.values.fill().0.resolve(
542 acquired_nodes,
543 &self.span_element_name,
544 self.values.fill_opacity().0,
545 self.values.color().0,
546 None,
547 None,
548 &layout_context.session,
549 );
550
551 let paint_order = self.values.paint_order();
552 let text_rendering = self.values.text_rendering();
553
554 LayoutSpan {
555 layout,
556 gravity,
557 extents,
558 is_visible,
559 x,
560 y,
561 paint_order,
562 stroke,
563 stroke_paint,
564 fill_paint,
565 text_rendering,
566 values: self.values.clone(),
567 link_target: self.link_target.clone(),
568 }
569 }
570}
571
572fn children_to_chunks(
576 chunks: &mut Vec<Chunk>,
577 node: &Node,
578 acquired_nodes: &mut AcquiredNodes<'_>,
579 cascaded: &CascadedValues<'_>,
580 layout_context: &LayoutContext,
581 dx: f64,
582 dy: f64,
583 depth: usize,
584 link: Option<String>,
585) {
586 let mut dx = dx;
587 let mut dy = dy;
588
589 let node_name = Rc::new(format!("{node}"));
590
591 for child in node.children() {
592 if child.is_chars() {
593 let values = cascaded.get();
594 child.borrow_chars().to_chunks(
595 &child,
596 node_name.clone(),
597 Rc::new(values.clone()),
598 chunks,
599 dx,
600 dy,
601 depth,
602 link.clone(),
603 );
604 } else {
605 assert!(child.is_element());
606
607 match *child.borrow_element_data() {
608 ElementData::TSpan(ref tspan) => {
609 let cascaded = CascadedValues::clone_with_node(cascaded, &child);
610 tspan.to_chunks(
611 &child,
612 acquired_nodes,
613 &cascaded,
614 layout_context,
615 chunks,
616 dx,
617 dy,
618 depth + 1,
619 link.clone(),
620 );
621 }
622
623 ElementData::Link(ref link) => {
624 let tspan = TSpan::default();
633 let cascaded = CascadedValues::clone_with_node(cascaded, &child);
634 tspan.to_chunks(
635 &child,
636 acquired_nodes,
637 &cascaded,
638 layout_context,
639 chunks,
640 dx,
641 dy,
642 depth + 1,
643 link.link.clone(),
644 );
645 }
646
647 ElementData::TRef(ref tref) => {
648 let cascaded = CascadedValues::clone_with_node(cascaded, &child);
649 tref.to_chunks(
650 &child,
651 acquired_nodes,
652 &cascaded,
653 chunks,
654 depth + 1,
655 layout_context,
656 );
657 }
658
659 _ => (),
660 }
661 }
662
663 dx = 0.0;
665 dy = 0.0;
666 }
667}
668
669#[derive(Default)]
693pub struct Chars {
694 string: RefCell<String>,
695 space_normalized: RefCell<Option<String>>,
696}
697
698impl Chars {
699 pub fn new(initial_text: &str) -> Chars {
700 Chars {
701 string: RefCell::new(String::from(initial_text)),
702 space_normalized: RefCell::new(None),
703 }
704 }
705
706 pub fn is_empty(&self) -> bool {
707 self.string.borrow().is_empty()
708 }
709
710 pub fn append(&self, s: &str) {
711 self.string.borrow_mut().push_str(s);
712 *self.space_normalized.borrow_mut() = None;
713 }
714
715 fn ensure_normalized_string(&self, node: &Node, values: &ComputedValues) {
716 let mut normalized = self.space_normalized.borrow_mut();
717
718 if (*normalized).is_none() {
719 let mode = match values.xml_space() {
720 XmlSpace::Default => XmlSpaceNormalize::Default(NormalizeDefault {
721 has_element_before: node.previous_sibling().is_some(),
722 has_element_after: node.next_sibling().is_some(),
723 }),
724
725 XmlSpace::Preserve => XmlSpaceNormalize::Preserve,
726 };
727
728 *normalized = Some(xml_space_normalize(mode, &self.string.borrow()));
729 }
730 }
731
732 fn make_span(
733 &self,
734 node: &Node,
735 span_element_name: Rc<String>,
736 values: Rc<ComputedValues>,
737 dx: f64,
738 dy: f64,
739 depth: usize,
740 link_target: Option<String>,
741 ) -> Span {
742 self.ensure_normalized_string(node, &values);
743
744 Span::new(
745 self.space_normalized.borrow().as_ref().unwrap(),
746 span_element_name,
747 values,
748 dx,
749 dy,
750 depth,
751 link_target,
752 )
753 }
754
755 fn to_chunks(
756 &self,
757 node: &Node,
758 span_element_name: Rc<String>,
759 values: Rc<ComputedValues>,
760 chunks: &mut [Chunk],
761 dx: f64,
762 dy: f64,
763 depth: usize,
764 link_target: Option<String>,
765 ) {
766 let span = self.make_span(node, span_element_name, values, dx, dy, depth, link_target);
767 let num_chunks = chunks.len();
768 assert!(num_chunks > 0);
769
770 chunks[num_chunks - 1].spans.push(span);
771 }
772
773 pub fn get_string(&self) -> String {
774 self.string.borrow().clone()
775 }
776}
777
778#[derive(Default)]
779pub struct Text {
780 x: Length<Horizontal>,
781 y: Length<Vertical>,
782 dx: Length<Horizontal>,
783 dy: Length<Vertical>,
784}
785
786impl Text {
787 fn make_chunks(
788 &self,
789 node: &Node,
790 acquired_nodes: &mut AcquiredNodes<'_>,
791 cascaded: &CascadedValues<'_>,
792 layout_context: &LayoutContext,
793 x: f64,
794 y: f64,
795 ) -> Vec<Chunk> {
796 let mut chunks = Vec::new();
797
798 let values = cascaded.get();
799 let params = NormalizeParams::new(values, &layout_context.viewport);
800
801 chunks.push(Chunk::new(values, Some(x), Some(y)));
802
803 let dx = self.dx.to_user(¶ms);
804 let dy = self.dy.to_user(¶ms);
805
806 children_to_chunks(
807 &mut chunks,
808 node,
809 acquired_nodes,
810 cascaded,
811 layout_context,
812 dx,
813 dy,
814 0,
815 None,
816 );
817 chunks
818 }
819
820 pub fn layout_text_spans(
821 &self,
822 node: &Node,
823 acquired_nodes: &mut AcquiredNodes<'_>,
824 cascaded: &CascadedValues<'_>,
825 viewport: &Viewport,
826 font_options: FontOptions,
827 session: &Session,
828 ) -> layout::Text {
829 let values = cascaded.get();
830 let params = NormalizeParams::new(values, viewport);
831
832 let layout_context = LayoutContext {
833 writing_mode: values.writing_mode(),
834 font_options,
835 viewport: *viewport,
836 session: session.clone(),
837 };
838
839 let mut x = self.x.to_user(¶ms);
840 let mut y = self.y.to_user(¶ms);
841
842 let chunks = self.make_chunks(node, acquired_nodes, cascaded, &layout_context, x, y);
843
844 let mut measured_chunks = Vec::new();
845 for chunk in &chunks {
846 measured_chunks.push(MeasuredChunk::from_chunk(&layout_context, chunk));
847 }
848
849 let mut positioned_chunks = Vec::new();
850 for chunk in &measured_chunks {
851 let chunk_x = chunk.x.unwrap_or(x);
852 let chunk_y = chunk.y.unwrap_or(y);
853
854 let positioned =
855 PositionedChunk::from_measured(&layout_context, chunk, chunk_x, chunk_y);
856
857 x = positioned.next_chunk_x;
858 y = positioned.next_chunk_y;
859
860 positioned_chunks.push(positioned);
861 }
862
863 let mut layout_spans = Vec::new();
864 for chunk in &positioned_chunks {
865 for span in &chunk.spans {
866 layout_spans.push(span.layout(&layout_context, acquired_nodes));
867 }
868 }
869
870 let text_extents: Option<Rect> = layout_spans
871 .iter()
872 .map(|span| span.extents)
873 .reduce(|a, b| match (a, b) {
874 (None, None) => None,
875 (None, Some(b)) => Some(b),
876 (Some(a), None) => Some(a),
877 (Some(a), Some(b)) => Some(a.union(&b)),
878 })
879 .flatten();
880
881 let mut text_spans = Vec::new();
882 for span in layout_spans {
883 let normalize_values = NormalizeValues::new(&span.values);
884
885 let stroke_paint = span.stroke_paint.to_user_space(
886 &text_extents,
887 &layout_context.viewport,
888 &normalize_values,
889 );
890 let fill_paint = span.fill_paint.to_user_space(
891 &text_extents,
892 &layout_context.viewport,
893 &normalize_values,
894 );
895
896 let text_span = TextSpan {
897 layout: span.layout,
898 gravity: span.gravity,
899 extents: span.extents,
900 is_visible: span.is_visible,
901 x: span.x,
902 y: span.y,
903 paint_order: span.paint_order,
904 stroke: span.stroke,
905 stroke_paint,
906 fill_paint,
907 text_rendering: span.text_rendering,
908 link_target: span.link_target,
909 };
910
911 text_spans.push(text_span);
912 }
913
914 layout::Text {
915 spans: text_spans,
916 extents: text_extents,
917 }
918 }
919}
920
921fn parse_list_and_extract_first<T: Copy + Default + Parse>(
928 dest: &mut T,
929 attr: QualName,
930 value: &str,
931 session: &Session,
932) {
933 let mut list: CommaSeparatedList<T, 0, 1024> = CommaSeparatedList(Vec::new());
934
935 set_attribute(&mut list, attr.parse(value), session);
936 if list.0.is_empty() {
937 *dest = Default::default();
938 } else {
939 *dest = list.0[0]; }
941}
942
943impl ElementTrait for Text {
944 fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
945 for (attr, value) in attrs.iter() {
946 match attr.expanded() {
947 expanded_name!("", "x") => {
948 parse_list_and_extract_first(&mut self.x, attr, value, session)
949 }
950 expanded_name!("", "y") => {
951 parse_list_and_extract_first(&mut self.y, attr, value, session)
952 }
953 expanded_name!("", "dx") => {
954 parse_list_and_extract_first(&mut self.dx, attr, value, session)
955 }
956 expanded_name!("", "dy") => {
957 parse_list_and_extract_first(&mut self.dy, attr, value, session)
958 }
959 _ => (),
960 }
961 }
962 }
963
964 fn layout(
965 &self,
966 node: &Node,
967 acquired_nodes: &mut AcquiredNodes<'_>,
968 cascaded: &CascadedValues<'_>,
969 viewport: &Viewport,
970 draw_ctx: &mut DrawingCtx,
971 ) -> Result<Option<Layer>, Box<InternalRenderingError>> {
972 let session = draw_ctx.session();
973
974 let values = cascaded.get();
975
976 let elt = node.borrow_element();
977
978 let stacking_ctx = StackingContext::new(
979 draw_ctx,
980 acquired_nodes,
981 &elt,
982 values.transform(),
983 None,
984 values,
985 viewport,
986 );
987
988 let font_options = draw_ctx.get_font_options();
989 let layout_text = self.layout_text_spans(
990 node,
991 acquired_nodes,
992 cascaded,
993 viewport,
994 font_options,
995 session,
996 );
997
998 Ok(Some(Layer {
999 kind: LayerKind::Text(Box::new(layout_text)),
1000 stacking_ctx,
1001 }))
1002 }
1003
1004 fn draw(
1005 &self,
1006 node: &Node,
1007 acquired_nodes: &mut AcquiredNodes<'_>,
1008 cascaded: &CascadedValues<'_>,
1009 viewport: &Viewport,
1010 draw_ctx: &mut DrawingCtx,
1011 clipping: bool,
1012 ) -> DrawResult {
1013 self.layout(node, acquired_nodes, cascaded, viewport, draw_ctx)
1014 .and_then(|layer| {
1015 draw_ctx.draw_layer(layer.as_ref().unwrap(), acquired_nodes, clipping, viewport)
1016 })
1017 }
1018}
1019
1020#[derive(Default)]
1021pub struct TRef {
1022 link: Option<NodeId>,
1023}
1024
1025impl TRef {
1026 fn to_chunks(
1027 &self,
1028 node: &Node,
1029 acquired_nodes: &mut AcquiredNodes<'_>,
1030 cascaded: &CascadedValues<'_>,
1031 chunks: &mut Vec<Chunk>,
1032 depth: usize,
1033 layout_context: &LayoutContext,
1034 ) {
1035 if self.link.is_none() {
1036 return;
1037 }
1038
1039 let link = self.link.as_ref().unwrap();
1040
1041 let values = cascaded.get();
1042 if !values.is_displayed() {
1043 return;
1044 }
1045
1046 let tref_element_name = format!("{node}");
1047
1048 if let Ok(acquired) = acquired_nodes.acquire(&tref_element_name, link) {
1049 let c = acquired.get();
1050 extract_chars_children_to_chunks_recursively(chunks, c, Rc::new(values.clone()), depth);
1051 } else {
1052 rsvg_log!(
1053 layout_context.session,
1054 "element {} references a nonexistent text source \"{}\"",
1055 node,
1056 link,
1057 );
1058 }
1059 }
1060}
1061
1062fn extract_chars_children_to_chunks_recursively(
1063 chunks: &mut Vec<Chunk>,
1064 node: &Node,
1065 values: Rc<ComputedValues>,
1066 depth: usize,
1067) {
1068 for child in node.children() {
1069 let values = values.clone();
1070
1071 if child.is_chars() {
1072 let span_element_name = Rc::new(format!("{node}"));
1073 child.borrow_chars().to_chunks(
1074 &child,
1075 span_element_name,
1076 values,
1077 chunks,
1078 0.0,
1079 0.0,
1080 depth,
1081 None,
1082 )
1083 } else {
1084 extract_chars_children_to_chunks_recursively(chunks, &child, values, depth + 1)
1085 }
1086 }
1087}
1088
1089impl ElementTrait for TRef {
1090 fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) {
1091 self.link = attrs
1092 .iter()
1093 .find(|(attr, _)| attr.expanded() == expanded_name!(xlink "href"))
1094 .and_then(|(attr, value)| NodeId::parse(value).attribute(attr).ok());
1098 }
1099}
1100
1101#[derive(Default)]
1102pub struct TSpan {
1103 x: Option<Length<Horizontal>>,
1104 y: Option<Length<Vertical>>,
1105 dx: Length<Horizontal>,
1106 dy: Length<Vertical>,
1107}
1108
1109impl TSpan {
1110 fn to_chunks(
1111 &self,
1112 node: &Node,
1113 acquired_nodes: &mut AcquiredNodes<'_>,
1114 cascaded: &CascadedValues<'_>,
1115 layout_context: &LayoutContext,
1116 chunks: &mut Vec<Chunk>,
1117 dx: f64,
1118 dy: f64,
1119 depth: usize,
1120 link: Option<String>,
1121 ) {
1122 let values = cascaded.get();
1123 if !values.is_displayed() {
1124 return;
1125 }
1126
1127 let params = NormalizeParams::new(values, &layout_context.viewport);
1128
1129 let x = self.x.map(|l| l.to_user(¶ms));
1130 let y = self.y.map(|l| l.to_user(¶ms));
1131
1132 let span_dx = dx + self.dx.to_user(¶ms);
1133 let span_dy = dy + self.dy.to_user(¶ms);
1134
1135 if x.is_some() || y.is_some() {
1136 chunks.push(Chunk::new(values, x, y));
1137 }
1138
1139 children_to_chunks(
1140 chunks,
1141 node,
1142 acquired_nodes,
1143 cascaded,
1144 layout_context,
1145 span_dx,
1146 span_dy,
1147 depth,
1148 link,
1149 );
1150 }
1151}
1152
1153impl ElementTrait for TSpan {
1154 fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
1155 for (attr, value) in attrs.iter() {
1156 match attr.expanded() {
1157 expanded_name!("", "x") => {
1158 parse_list_and_extract_first(&mut self.x, attr, value, session)
1159 }
1160 expanded_name!("", "y") => {
1161 parse_list_and_extract_first(&mut self.y, attr, value, session)
1162 }
1163 expanded_name!("", "dx") => {
1164 parse_list_and_extract_first(&mut self.dx, attr, value, session)
1165 }
1166 expanded_name!("", "dy") => {
1167 parse_list_and_extract_first(&mut self.dy, attr, value, session)
1168 }
1169 _ => (),
1170 }
1171 }
1172 }
1173}
1174
1175impl From<FontStyle> for pango::Style {
1176 fn from(s: FontStyle) -> pango::Style {
1177 match s {
1178 FontStyle::Normal => pango::Style::Normal,
1179 FontStyle::Italic => pango::Style::Italic,
1180 FontStyle::Oblique => pango::Style::Oblique,
1181 }
1182 }
1183}
1184
1185impl From<FontVariant> for pango::Variant {
1186 fn from(v: FontVariant) -> pango::Variant {
1187 match v {
1188 FontVariant::Normal => pango::Variant::Normal,
1189 FontVariant::SmallCaps => pango::Variant::SmallCaps,
1190 }
1191 }
1192}
1193
1194impl From<FontStretch> for pango::Stretch {
1195 fn from(s: FontStretch) -> pango::Stretch {
1196 match s {
1197 FontStretch::Normal => pango::Stretch::Normal,
1198 FontStretch::Wider => pango::Stretch::Expanded, FontStretch::Narrower => pango::Stretch::Condensed, FontStretch::UltraCondensed => pango::Stretch::UltraCondensed,
1201 FontStretch::ExtraCondensed => pango::Stretch::ExtraCondensed,
1202 FontStretch::Condensed => pango::Stretch::Condensed,
1203 FontStretch::SemiCondensed => pango::Stretch::SemiCondensed,
1204 FontStretch::SemiExpanded => pango::Stretch::SemiExpanded,
1205 FontStretch::Expanded => pango::Stretch::Expanded,
1206 FontStretch::ExtraExpanded => pango::Stretch::ExtraExpanded,
1207 FontStretch::UltraExpanded => pango::Stretch::UltraExpanded,
1208 }
1209 }
1210}
1211
1212impl From<FontWeight> for pango::Weight {
1213 fn from(w: FontWeight) -> pango::Weight {
1214 pango::Weight::__Unknown(w.numeric_weight().into())
1215 }
1216}
1217
1218impl From<Direction> for pango::Direction {
1219 fn from(d: Direction) -> pango::Direction {
1220 match d {
1221 Direction::Ltr => pango::Direction::Ltr,
1222 Direction::Rtl => pango::Direction::Rtl,
1223 }
1224 }
1225}
1226
1227impl From<WritingMode> for pango::Direction {
1228 fn from(m: WritingMode) -> pango::Direction {
1229 use WritingMode::*;
1230 match m {
1231 HorizontalTb | VerticalRl | VerticalLr | LrTb | Lr | Tb | TbRl => pango::Direction::Ltr,
1232 RlTb | Rl => pango::Direction::Rtl,
1233 }
1234 }
1235}
1236
1237impl From<WritingMode> for pango::Gravity {
1238 fn from(m: WritingMode) -> pango::Gravity {
1239 use WritingMode::*;
1240 match m {
1241 HorizontalTb | LrTb | Lr | RlTb | Rl => pango::Gravity::South,
1242 VerticalRl | Tb | TbRl => pango::Gravity::East,
1243 VerticalLr => pango::Gravity::West,
1244 }
1245 }
1246}
1247
1248pub mod directional_formatting_characters {
1252 pub const LRE: char = '\u{202a}';
1256
1257 pub const RLE: char = '\u{202b}';
1261
1262 pub const LRO: char = '\u{202d}';
1266
1267 pub const RLO: char = '\u{202e}';
1271
1272 pub const PDF: char = '\u{202c}';
1276
1277 pub const LRI: char = '\u{2066}';
1281
1282 pub const RLI: char = '\u{2067}';
1286
1287 pub const FSI: char = '\u{2068}';
1292
1293 pub const PDI: char = '\u{2069}';
1297}
1298
1299pub struct BidiControl {
1306 pub start: &'static [char],
1307 pub end: &'static [char],
1308}
1309
1310impl BidiControl {
1311 #[rustfmt::skip]
1316 pub fn from_unicode_bidi_and_direction(unicode_bidi: UnicodeBidi, direction: Direction) -> BidiControl {
1317 use UnicodeBidi::*;
1318 use Direction::*;
1319 use directional_formatting_characters::*;
1320
1321 let (start, end) = match (unicode_bidi, direction) {
1322 (Normal, _) => (&[][..], &[][..]),
1323 (Embed, Ltr) => (&[LRE][..], &[PDF][..]),
1324 (Embed, Rtl) => (&[RLE][..], &[PDF][..]),
1325 (Isolate, Ltr) => (&[LRI][..], &[PDI][..]),
1326 (Isolate, Rtl) => (&[RLI][..], &[PDI][..]),
1327 (BidiOverride, Ltr) => (&[LRO][..], &[PDF][..]),
1328 (BidiOverride, Rtl) => (&[RLO][..], &[PDF][..]),
1329 (IsolateOverride, Ltr) => (&[FSI, LRO][..], &[PDF, PDI][..]),
1330 (IsolateOverride, Rtl) => (&[FSI, RLO][..], &[PDF, PDI][..]),
1331 (Plaintext, Ltr) => (&[FSI][..], &[PDI][..]),
1332 (Plaintext, Rtl) => (&[FSI][..], &[PDI][..]),
1333 };
1334
1335 BidiControl { start, end }
1336 }
1337}
1338
1339fn wrap_with_direction_control_chars(s: &str, bidi_control: &BidiControl) -> String {
1341 let mut res =
1342 String::with_capacity(s.len() + bidi_control.start.len() + bidi_control.end.len());
1343
1344 for &ch in bidi_control.start {
1345 res.push(ch);
1346 }
1347
1348 res.push_str(s);
1349
1350 for &ch in bidi_control.end {
1351 res.push(ch);
1352 }
1353
1354 res
1355}
1356
1357fn create_pango_layout(
1359 layout_context: &LayoutContext,
1360 props: &FontProperties,
1361 text: &str,
1362) -> Option<pango::Layout> {
1363 let pango_context = create_pango_context(&layout_context.font_options);
1364
1365 if let XmlLang(Some(ref lang)) = props.xml_lang {
1366 pango_context.set_language(Some(&pango::Language::from_string(lang.as_str())));
1367 }
1368
1369 pango_context.set_base_gravity(pango::Gravity::from(layout_context.writing_mode));
1370
1371 match (props.unicode_bidi, props.direction) {
1372 (UnicodeBidi::BidiOverride, _) | (UnicodeBidi::Embed, _) => {
1373 pango_context.set_base_dir(pango::Direction::from(props.direction));
1374 }
1375
1376 (_, direction) if direction != Direction::Ltr => {
1377 pango_context.set_base_dir(pango::Direction::from(direction));
1378 }
1379
1380 (_, _) => {
1381 pango_context.set_base_dir(pango::Direction::from(layout_context.writing_mode));
1382 }
1383 }
1384
1385 let layout = pango::Layout::new(&pango_context);
1386
1387 let font_size = PangoUnits::from_pixels(props.font_size);
1388 let letter_spacing = PangoUnits::from_pixels(props.letter_spacing);
1389
1390 if font_size.is_none() {
1391 rsvg_log!(
1392 &layout_context.session,
1393 "font-size {} is out of bounds; ignoring span",
1394 props.font_size
1395 );
1396 }
1397
1398 if letter_spacing.is_none() {
1399 rsvg_log!(
1400 &layout_context.session,
1401 "letter-spacing {} is out of bounds; ignoring span",
1402 props.letter_spacing
1403 );
1404 }
1405
1406 if let (Some(font_size), Some(letter_spacing)) = (font_size, letter_spacing) {
1407 let attr_list = pango::AttrList::new();
1408 add_pango_attributes(&attr_list, props, 0, text.len(), font_size, letter_spacing);
1409
1410 layout.set_attributes(Some(&attr_list));
1411 layout.set_text(text);
1412 layout.set_auto_dir(false);
1413
1414 Some(layout)
1415 } else {
1416 None
1417 }
1418}
1419
1420fn add_pango_attributes(
1422 attr_list: &pango::AttrList,
1423 props: &FontProperties,
1424 start_index: usize,
1425 end_index: usize,
1426 font_size: PangoUnits,
1427 letter_spacing: PangoUnits,
1428) {
1429 let start_index = u32::try_from(start_index).expect("Pango attribute index must fit in u32");
1430 let end_index = u32::try_from(end_index).expect("Pango attribute index must fit in u32");
1431 assert!(start_index <= end_index);
1432
1433 let mut attributes = Vec::new();
1434
1435 let mut font_desc = pango::FontDescription::new();
1436 font_desc.set_family(props.font_family.as_str());
1437 font_desc.set_style(pango::Style::from(props.font_style));
1438
1439 font_desc.set_variant(pango::Variant::from(props.font_variant));
1440
1441 font_desc.set_weight(pango::Weight::from(props.font_weight));
1442 font_desc.set_stretch(pango::Stretch::from(props.font_stretch));
1443
1444 font_desc.set_size(font_size.0);
1445
1446 attributes.push(pango::AttrFontDesc::new(&font_desc).upcast());
1447
1448 attributes.push(pango::AttrInt::new_letter_spacing(letter_spacing.0).upcast());
1449
1450 if props.text_decoration.overline {
1451 attributes.push(pango::AttrInt::new_overline(pango::Overline::Single).upcast());
1452 }
1453
1454 if props.text_decoration.underline {
1455 attributes.push(pango::AttrInt::new_underline(pango::Underline::Single).upcast());
1456 }
1457
1458 if props.text_decoration.strike {
1459 attributes.push(pango::AttrInt::new_strikethrough(true).upcast());
1460 }
1461
1462 for attr in &mut attributes {
1465 attr.set_start_index(start_index);
1466 attr.set_end_index(end_index);
1467 }
1468
1469 for attr in attributes {
1472 attr_list.insert(attr);
1473 }
1474}
1475
1476#[cfg(test)]
1477mod tests {
1478 use super::*;
1479
1480 #[test]
1481 fn chars_default() {
1482 let c = Chars::default();
1483 assert!(c.is_empty());
1484 assert!(c.space_normalized.borrow().is_none());
1485 }
1486
1487 #[test]
1488 fn chars_new() {
1489 let example = "Test 123";
1490 let c = Chars::new(example);
1491 assert_eq!(c.get_string(), example);
1492 assert!(c.space_normalized.borrow().is_none());
1493 }
1494
1495 #[test]
1498 fn adjusted_advance_horizontal_ltr() {
1499 use Direction::*;
1500 use TextAnchor::*;
1501
1502 assert_eq!(
1503 text_anchor_offset(
1504 Start,
1505 Ltr,
1506 WritingMode::Lr,
1507 Rect::from_size(1.0, 2.0).translate((5.0, 6.0))
1508 ),
1509 (-5.0, 0.0)
1510 );
1511
1512 assert_eq!(
1513 text_anchor_offset(
1514 Middle,
1515 Ltr,
1516 WritingMode::Lr,
1517 Rect::from_size(1.0, 2.0).translate((5.0, 6.0))
1518 ),
1519 (-5.5, 0.0)
1520 );
1521
1522 assert_eq!(
1523 text_anchor_offset(
1524 End,
1525 Ltr,
1526 WritingMode::Lr,
1527 Rect::from_size(1.0, 2.0).translate((5.0, 6.0))
1528 ),
1529 (-6.0, 0.0)
1530 );
1531 }
1532
1533 #[test]
1534 fn adjusted_advance_horizontal_rtl() {
1535 use Direction::*;
1536 use TextAnchor::*;
1537
1538 assert_eq!(
1539 text_anchor_offset(
1540 Start,
1541 Rtl,
1542 WritingMode::Rl,
1543 Rect::from_size(1.0, 2.0).translate((5.0, 6.0))
1544 ),
1545 (-6.0, 0.0)
1546 );
1547 assert_eq!(
1548 text_anchor_offset(
1549 Middle,
1550 Rtl,
1551 WritingMode::Rl,
1552 Rect::from_size(1.0, 2.0).translate((5.0, 6.0))
1553 ),
1554 (-5.5, 0.0)
1555 );
1556 assert_eq!(
1557 text_anchor_offset(
1558 TextAnchor::End,
1559 Direction::Rtl,
1560 WritingMode::Rl,
1561 Rect::from_size(1.0, 2.0).translate((5.0, 6.0))
1562 ),
1563 (-5.0, 0.0)
1564 );
1565 }
1566
1567 #[test]
1572 fn adjusted_advance_vertical() {
1573 use Direction::*;
1574 use TextAnchor::*;
1575
1576 assert_eq!(
1577 text_anchor_offset(Start, Ltr, WritingMode::Tb, Rect::from_size(2.0, 4.0)),
1578 (0.0, 0.0)
1579 );
1580
1581 assert_eq!(
1582 text_anchor_offset(Middle, Ltr, WritingMode::Tb, Rect::from_size(2.0, 4.0)),
1583 (0.0, -2.0)
1584 );
1585
1586 assert_eq!(
1587 text_anchor_offset(End, Ltr, WritingMode::Tb, Rect::from_size(2.0, 4.0)),
1588 (0.0, -4.0)
1589 );
1590 }
1591
1592 #[test]
1593 fn pango_units_works() {
1594 assert_eq!(PangoUnits::from_pixels(10.0).unwrap().0, pango::SCALE * 10);
1595 }
1596
1597 #[test]
1598 fn pango_units_detects_overflow() {
1599 assert!(PangoUnits::from_pixels(1e7).is_none());
1600 }
1601}