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