1
//! Text elements: `text`, `tspan`, `tref`.
2

            
3
use markup5ever::{expanded_name, local_name, ns, QualName};
4
use pango::prelude::FontExt;
5
use pango::IsAttribute;
6
use std::cell::RefCell;
7
use std::convert::TryFrom;
8
use std::rc::Rc;
9

            
10
use crate::bbox::BoundingBox;
11
use crate::document::{AcquiredNodes, NodeId};
12
use crate::drawing_ctx::{create_pango_context, DrawingCtx, FontOptions, Viewport};
13
use crate::element::{set_attribute, ElementData, ElementTrait};
14
use crate::error::*;
15
use crate::layout::{self, FontProperties, Layer, LayerKind, StackingContext, Stroke, TextSpan};
16
use crate::length::*;
17
use crate::node::{CascadedValues, Node, NodeBorrow};
18
use crate::paint_server::PaintSource;
19
use crate::parsers::{CommaSeparatedList, Parse, ParseValue};
20
use crate::properties::{
21
    ComputedValues, Direction, DominantBaseline, FontStretch, FontStyle, FontVariant, FontWeight,
22
    PaintOrder, TextAnchor, TextRendering, UnicodeBidi, WritingMode, XmlLang, XmlSpace,
23
};
24
use crate::rect::Rect;
25
use crate::rsvg_log;
26
use crate::session::Session;
27
use crate::space::{xml_space_normalize, NormalizeDefault, XmlSpaceNormalize};
28
use crate::xml::Attributes;
29

            
30
/// The state of a text layout operation.
31
struct LayoutContext {
32
    /// `writing-mode` property from the `<text>` element.
33
    writing_mode: WritingMode,
34

            
35
    /// Font options from the DrawingCtx.
36
    font_options: FontOptions,
37

            
38
    /// For normalizing lengths.
39
    viewport: Viewport,
40

            
41
    /// Session metadata for the document
42
    session: Session,
43
}
44

            
45
/// An absolutely-positioned array of `Span`s
46
///
47
/// SVG defines a "[text chunk]" to occur when a text-related element
48
/// has an absolute position adjustment, that is, `x` or `y`
49
/// attributes.
50
///
51
/// A `<text>` element always starts with an absolute position from
52
/// such attributes, or (0, 0) if they are not specified.
53
///
54
/// Subsequent children of the `<text>` element will create new chunks
55
/// whenever they have `x` or `y` attributes.
56
///
57
/// [text chunk]: https://www.w3.org/TR/SVG11/text.html#TextLayoutIntroduction
58
struct Chunk {
59
    values: Rc<ComputedValues>,
60
    x: Option<f64>,
61
    y: Option<f64>,
62
    spans: Vec<Span>,
63
}
64

            
65
struct MeasuredChunk {
66
    values: Rc<ComputedValues>,
67
    x: Option<f64>,
68
    y: Option<f64>,
69
    dx: f64,
70
    dy: f64,
71
    spans: Vec<MeasuredSpan>,
72
}
73

            
74
struct PositionedChunk {
75
    next_chunk_x: f64,
76
    next_chunk_y: f64,
77
    spans: Vec<PositionedSpan>,
78
}
79

            
80
struct Span {
81
    values: Rc<ComputedValues>,
82
    text: String,
83
    dx: f64,
84
    dy: f64,
85
    _depth: usize,
86
    link_target: Option<String>,
87
}
88

            
89
struct 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
}
98

            
99
struct PositionedSpan {
100
    layout: pango::Layout,
101
    values: Rc<ComputedValues>,
102
    rendered_position: (f64, f64),
103
    next_span_position: (f64, f64),
104
    link_target: Option<String>,
105
}
106

            
107
/// A laid-out and resolved text span.
108
///
109
/// The only thing not in user-space units are the `stroke_paint` and `fill_paint`.
110
///
111
/// This is the non-user-space version of `layout::TextSpan`.
112
struct LayoutSpan {
113
    layout: pango::Layout,
114
    gravity: pango::Gravity,
115
    extents: Option<Rect>,
116
    is_visible: bool,
117
    x: f64,
118
    y: f64,
119
    paint_order: PaintOrder,
120
    stroke: Stroke,
121
    stroke_paint: Rc<PaintSource>,
122
    fill_paint: Rc<PaintSource>,
123
    text_rendering: TextRendering,
124
    link_target: Option<String>,
125
    values: Rc<ComputedValues>,
126
}
127

            
128
impl Chunk {
129
18183
    fn new(values: &ComputedValues, x: Option<f64>, y: Option<f64>) -> Chunk {
130
18183
        Chunk {
131
18183
            values: Rc::new(values.clone()),
132
18183
            x,
133
18183
            y,
134
18183
            spans: Vec::new(),
135
18183
        }
136
18183
    }
137
}
138

            
139
impl MeasuredChunk {
140
18183
    fn from_chunk(layout_context: &LayoutContext, chunk: &Chunk) -> MeasuredChunk {
141
18183
        let mut measured_spans: Vec<MeasuredSpan> = chunk
142
18183
            .spans
143
18183
            .iter()
144
19931
            .filter_map(|span| MeasuredSpan::from_span(layout_context, span))
145
18183
            .collect();
146

            
147
        // The first span contains the (dx, dy) that will be applied to the whole chunk.
148
        // Make them 0 in the span, and extract the values to set them on the chunk.
149
        // This is a hack until librsvg adds support for multiple dx/dy values per text/tspan.
150

            
151
18183
        let (chunk_dx, chunk_dy) = if let Some(first) = measured_spans.first_mut() {
152
18107
            let dx = first.dx;
153
18107
            let dy = first.dy;
154
18107
            first.dx = 0.0;
155
18107
            first.dy = 0.0;
156
18107
            (dx, dy)
157
        } else {
158
76
            (0.0, 0.0)
159
        };
160

            
161
18183
        MeasuredChunk {
162
18183
            values: chunk.values.clone(),
163
18183
            x: chunk.x,
164
18183
            y: chunk.y,
165
18183
            dx: chunk_dx,
166
18183
            dy: chunk_dy,
167
18183
            spans: measured_spans,
168
18183
        }
169
18183
    }
170
}
171

            
172
impl PositionedChunk {
173
18183
    fn from_measured(
174
18183
        layout_context: &LayoutContext,
175
18183
        measured: &MeasuredChunk,
176
18183
        chunk_x: f64,
177
18183
        chunk_y: f64,
178
18183
    ) -> PositionedChunk {
179
18183
        let chunk_direction = measured.values.direction();
180
18183

            
181
18183
        // Position the spans relatively to each other, starting at (0, 0)
182
18183

            
183
18183
        let mut positioned = Vec::new();
184
18183

            
185
18183
        // Start position of each span; gets advanced as each span is laid out.
186
18183
        // This is the text's start position, not the bounding box.
187
18183
        let mut x = 0.0;
188
18183
        let mut y = 0.0;
189
18183

            
190
18183
        let mut chunk_bounds: Option<Rect> = None;
191

            
192
        // Find the bounding box of the entire chunk by taking the union of the bounding boxes
193
        // of each individual span.
194

            
195
38076
        for mspan in &measured.spans {
196
19893
            let params = NormalizeParams::new(&mspan.values, &layout_context.viewport);
197
19893

            
198
19893
            let layout = mspan.layout.clone();
199
19893
            let layout_size = mspan.layout_size;
200
19893
            let values = mspan.values.clone();
201
19893
            let dx = mspan.dx;
202
19893
            let dy = mspan.dy;
203
19893
            let advance = mspan.advance;
204
19893

            
205
19893
            let baseline_offset = compute_baseline_offset(&layout, &values, &params);
206

            
207
19893
            let start_pos = match chunk_direction {
208
19779
                Direction::Ltr => (x, y),
209
114
                Direction::Rtl => (x - advance.0, y),
210
            };
211

            
212
19893
            let span_advance = match chunk_direction {
213
19779
                Direction::Ltr => (advance.0, advance.1),
214
114
                Direction::Rtl => (-advance.0, advance.1),
215
            };
216

            
217
19893
            let rendered_position = if layout_context.writing_mode.is_horizontal() {
218
19893
                (start_pos.0 + dx, start_pos.1 - baseline_offset + dy)
219
            } else {
220
                (start_pos.0 + baseline_offset + dx, start_pos.1 + dy)
221
            };
222

            
223
19893
            let span_bounds =
224
19893
                Rect::from_size(layout_size.0, layout_size.1).translate(rendered_position);
225

            
226
            // We take the union here
227

            
228
19893
            if let Some(bounds) = chunk_bounds {
229
1786
                chunk_bounds = Some(bounds.union(&span_bounds));
230
18107
            } else {
231
18107
                chunk_bounds = Some(span_bounds);
232
18107
            }
233

            
234
19893
            x = x + span_advance.0 + dx;
235
19893
            y = y + span_advance.1 + dy;
236
19893

            
237
19893
            let positioned_span = PositionedSpan {
238
19893
                layout,
239
19893
                values,
240
19893
                rendered_position,
241
19893
                next_span_position: (x, y),
242
19893
                link_target: mspan.link_target.clone(),
243
19893
            };
244
19893

            
245
19893
            positioned.push(positioned_span);
246
        }
247

            
248
        // Compute the offsets needed to align the chunk per the text-anchor property (start, middle, end):
249

            
250
18183
        let anchor_offset = text_anchor_offset(
251
18183
            measured.values.text_anchor(),
252
18183
            chunk_direction,
253
18183
            layout_context.writing_mode,
254
18183
            chunk_bounds.unwrap_or_default(),
255
18183
        );
256
18183

            
257
18183
        // Apply the text-anchor offset to each individually-positioned span, and compute the
258
18183
        // start position of the next chunk.  Also add in the chunk's dx/dy.
259
18183

            
260
18183
        let mut next_chunk_x = chunk_x;
261
18183
        let mut next_chunk_y = chunk_y;
262

            
263
38076
        for pspan in &mut positioned {
264
19893
            // Add the chunk's position, plus the text-anchor offset, plus the chunk's dx/dy.
265
19893
            // This last term is a hack until librsvg adds support for multiple dx/dy values per text/tspan;
266
19893
            // see the corresponding part in MeasuredChunk::from_chunk().
267
19893
            pspan.rendered_position.0 += chunk_x + anchor_offset.0 + measured.dx;
268
19893
            pspan.rendered_position.1 += chunk_y + anchor_offset.1 + measured.dy;
269
19893

            
270
19893
            next_chunk_x = chunk_x + pspan.next_span_position.0 + anchor_offset.0 + measured.dx;
271
19893
            next_chunk_y = chunk_y + pspan.next_span_position.1 + anchor_offset.1 + measured.dy;
272
19893
        }
273

            
274
18183
        PositionedChunk {
275
18183
            next_chunk_x,
276
18183
            next_chunk_y,
277
18183
            spans: positioned,
278
18183
        }
279
18183
    }
280
}
281

            
282
19893
fn compute_baseline_offset(
283
19893
    layout: &pango::Layout,
284
19893
    values: &ComputedValues,
285
19893
    params: &NormalizeParams,
286
19893
) -> f64 {
287
19893
    let mut baseline = f64::from(layout.baseline()) / f64::from(pango::SCALE);
288
19893
    let dominant_baseline = values.dominant_baseline();
289
19893

            
290
19893
    let mut layout_iter = layout.iter();
291
    loop {
292
19893
        let run = layout_iter.run_readonly();
293
19893

            
294
19893
        if run.is_some() {
295
19114
            let item = run.unwrap().item();
296
19114
            unsafe {
297
19114
                let analysis = (*item.as_ptr()).analysis;
298
19114
                if analysis.font.is_null() {
299
                    break;
300
19114
                }
301
19114
            }
302
19114
            let font = item.analysis().font();
303
19114

            
304
19114
            let metrics = font.metrics(None);
305
19114
            let ascent = metrics.ascent();
306
19114
            let descent = metrics.descent();
307
19114
            let height = metrics.height();
308
19114

            
309
19114
            match dominant_baseline {
310
57
                DominantBaseline::Hanging => {
311
57
                    baseline -= f64::from(ascent - descent) / f64::from(pango::SCALE);
312
57
                }
313
57
                DominantBaseline::Middle => {
314
57
                    // Approximate meanline using strikethrough position and thickness
315
57
                    // https://mail.gnome.org/archives/gtk-i18n-list/2012-December/msg00046.html
316
57
                    baseline -= f64::from(
317
57
                        metrics.strikethrough_position() + metrics.strikethrough_thickness() / 2,
318
57
                    ) / f64::from(pango::SCALE);
319
57
                }
320
57
                DominantBaseline::Central => {
321
57
                    baseline = 0.5 * f64::from(ascent + descent) / f64::from(pango::SCALE);
322
57
                }
323
57
                DominantBaseline::TextBeforeEdge | DominantBaseline::TextTop => {
324
57
                    //baseline -= f64::from(ascent) / f64::from(pango::SCALE);
325
57
                    // Bit of a klutch, but leads to better results
326
57
                    baseline -= f64::from(2 * ascent - height) / f64::from(pango::SCALE);
327
57
                }
328
57
                DominantBaseline::TextAfterEdge | DominantBaseline::TextBottom => {
329
57
                    baseline += f64::from(descent) / f64::from(pango::SCALE);
330
57
                }
331
                DominantBaseline::Ideographic => {
332
                    // Approx
333
                    baseline += f64::from(descent) / f64::from(pango::SCALE);
334
                }
335
                DominantBaseline::Mathematical => {
336
                    // Approx
337
                    baseline = 0.5 * f64::from(ascent + descent) / f64::from(pango::SCALE);
338
                }
339
18829
                _ => (),
340
            }
341

            
342
19114
            break;
343
779
        }
344
779

            
345
779
        if !layout_iter.next_run() {
346
779
            break;
347
        }
348
    }
349

            
350
19893
    let baseline_shift = values.baseline_shift().0.to_user(params);
351
19893

            
352
19893
    baseline + baseline_shift
353
19893
}
354

            
355
/// Computes the (x, y) offsets to be applied to spans after applying the text-anchor property (start, middle, end).
356
#[rustfmt::skip]
357
18192
fn text_anchor_offset(
358
18192
    anchor: TextAnchor,
359
18192
    direction: Direction,
360
18192
    writing_mode: WritingMode,
361
18192
    chunk_bounds: Rect,
362
18192
) -> (f64, f64) {
363
18192
    let (w, h) = (chunk_bounds.width(), chunk_bounds.height());
364
18192

            
365
18192
    let x0 = chunk_bounds.x0;
366
18192

            
367
18192
    if writing_mode.is_horizontal() {
368
18189
        match (anchor, direction) {
369
14365
            (TextAnchor::Start,  Direction::Ltr) => (-x0, 0.0),
370
20
            (TextAnchor::Start,  Direction::Rtl) => (-x0 - w, 0.0),
371

            
372
3459
            (TextAnchor::Middle, Direction::Ltr) => (-x0 - w / 2.0, 0.0),
373
20
            (TextAnchor::Middle, Direction::Rtl) => (-x0 - w / 2.0, 0.0),
374

            
375
305
            (TextAnchor::End,    Direction::Ltr) => (-x0 - w, 0.0),
376
20
            (TextAnchor::End,    Direction::Rtl) => (-x0, 0.0),
377
        }
378
    } else {
379
        // FIXME: we don't deal with text direction for vertical text yet.
380
3
        match anchor {
381
1
            TextAnchor::Start => (0.0, 0.0),
382
1
            TextAnchor::Middle => (0.0, -h / 2.0),
383
1
            TextAnchor::End => (0.0, -h),
384
        }
385
    }
386
18192
}
387

            
388
impl Span {
389
19931
    fn new(
390
19931
        text: &str,
391
19931
        values: Rc<ComputedValues>,
392
19931
        dx: f64,
393
19931
        dy: f64,
394
19931
        depth: usize,
395
19931
        link_target: Option<String>,
396
19931
    ) -> Span {
397
19931
        Span {
398
19931
            values,
399
19931
            text: text.to_string(),
400
19931
            dx,
401
19931
            dy,
402
19931
            _depth: depth,
403
19931
            link_target,
404
19931
        }
405
19931
    }
406
}
407

            
408
/// Use as `PangoUnits::from_pixels()` so that we can check for overflow.
409
struct PangoUnits(i32);
410

            
411
impl PangoUnits {
412
39864
    fn from_pixels(v: f64) -> Option<Self> {
413
39864
        // We want (v * f64::from(pango::SCALE) + 0.5) as i32
414
39864
        //
415
39864
        // But check for overflow.
416
39864

            
417
39864
        cast::i32(v * f64::from(pango::SCALE) + 0.5)
418
39864
            .ok()
419
39864
            .map(PangoUnits)
420
39864
    }
421
}
422

            
423
impl MeasuredSpan {
424
19931
    fn from_span(layout_context: &LayoutContext, span: &Span) -> Option<MeasuredSpan> {
425
19931
        let values = span.values.clone();
426
19931

            
427
19931
        let params = NormalizeParams::new(&values, &layout_context.viewport);
428
19931

            
429
19931
        let properties = FontProperties::new(&values, &params);
430
19931

            
431
19931
        let bidi_control = BidiControl::from_unicode_bidi_and_direction(
432
19931
            properties.unicode_bidi,
433
19931
            properties.direction,
434
19931
        );
435
19931

            
436
19931
        let with_control_chars = wrap_with_direction_control_chars(&span.text, &bidi_control);
437

            
438
19931
        if let Some(layout) = create_pango_layout(layout_context, &properties, &with_control_chars)
439
        {
440
19893
            let (w, h) = layout.size();
441
19893

            
442
19893
            let w = f64::from(w) / f64::from(pango::SCALE);
443
19893
            let h = f64::from(h) / f64::from(pango::SCALE);
444

            
445
19893
            let advance = if layout_context.writing_mode.is_horizontal() {
446
19893
                (w, 0.0)
447
            } else {
448
                (0.0, w)
449
            };
450

            
451
19893
            Some(MeasuredSpan {
452
19893
                values,
453
19893
                layout,
454
19893
                layout_size: (w, h),
455
19893
                advance,
456
19893
                dx: span.dx,
457
19893
                dy: span.dy,
458
19893
                link_target: span.link_target.clone(),
459
19893
            })
460
        } else {
461
38
            None
462
        }
463
19931
    }
464
}
465

            
466
// FIXME: should the pango crate provide this like PANGO_GRAVITY_IS_VERTICAL() ?
467
18734
fn gravity_is_vertical(gravity: pango::Gravity) -> bool {
468
18734
    matches!(gravity, pango::Gravity::East | pango::Gravity::West)
469
18734
}
470

            
471
19893
fn compute_text_box(
472
19893
    layout: &pango::Layout,
473
19893
    x: f64,
474
19893
    y: f64,
475
19893
    gravity: pango::Gravity,
476
19893
) -> Option<Rect> {
477
19893
    #![allow(clippy::many_single_char_names)]
478
19893

            
479
19893
    let (ink, _) = layout.extents();
480
19893
    if ink.width() == 0 || ink.height() == 0 {
481
1159
        return None;
482
18734
    }
483
18734

            
484
18734
    let ink_x = f64::from(ink.x());
485
18734
    let ink_y = f64::from(ink.y());
486
18734
    let ink_width = f64::from(ink.width());
487
18734
    let ink_height = f64::from(ink.height());
488
18734
    let pango_scale = f64::from(pango::SCALE);
489

            
490
18734
    let (x, y, w, h) = if gravity_is_vertical(gravity) {
491
        (
492
            x + (ink_x - ink_height) / pango_scale,
493
            y + ink_y / pango_scale,
494
            ink_height / pango_scale,
495
            ink_width / pango_scale,
496
        )
497
    } else {
498
18734
        (
499
18734
            x + ink_x / pango_scale,
500
18734
            y + ink_y / pango_scale,
501
18734
            ink_width / pango_scale,
502
18734
            ink_height / pango_scale,
503
18734
        )
504
    };
505

            
506
18734
    Some(Rect::new(x, y, x + w, y + h))
507
19893
}
508

            
509
impl PositionedSpan {
510
19893
    fn layout(
511
19893
        &self,
512
19893
        layout_context: &LayoutContext,
513
19893
        acquired_nodes: &mut AcquiredNodes<'_>,
514
19893
    ) -> LayoutSpan {
515
19893
        let params = NormalizeParams::new(&self.values, &layout_context.viewport);
516
19893

            
517
19893
        let layout = self.layout.clone();
518
19893
        let is_visible = self.values.is_visible();
519
19893
        let (x, y) = self.rendered_position;
520
19893

            
521
19893
        let stroke = Stroke::new(&self.values, &params);
522
19893

            
523
19893
        let gravity = layout.context().gravity();
524
19893

            
525
19893
        let extents = compute_text_box(&layout, x, y, gravity);
526
19893

            
527
19893
        let stroke_paint = self.values.stroke().0.resolve(
528
19893
            acquired_nodes,
529
19893
            self.values.stroke_opacity().0,
530
19893
            self.values.color().0,
531
19893
            None,
532
19893
            None,
533
19893
            &layout_context.session,
534
19893
        );
535
19893

            
536
19893
        let fill_paint = self.values.fill().0.resolve(
537
19893
            acquired_nodes,
538
19893
            self.values.fill_opacity().0,
539
19893
            self.values.color().0,
540
19893
            None,
541
19893
            None,
542
19893
            &layout_context.session,
543
19893
        );
544
19893

            
545
19893
        let paint_order = self.values.paint_order();
546
19893
        let text_rendering = self.values.text_rendering();
547
19893

            
548
19893
        LayoutSpan {
549
19893
            layout,
550
19893
            gravity,
551
19893
            extents,
552
19893
            is_visible,
553
19893
            x,
554
19893
            y,
555
19893
            paint_order,
556
19893
            stroke,
557
19893
            stroke_paint,
558
19893
            fill_paint,
559
19893
            text_rendering,
560
19893
            values: self.values.clone(),
561
19893
            link_target: self.link_target.clone(),
562
19893
        }
563
19893
    }
564
}
565

            
566
/// Walks the children of a `<text>`, `<tspan>`, or `<tref>` element
567
/// and appends chunks/spans from them into the specified `chunks`
568
/// array.
569
19076
fn children_to_chunks(
570
19076
    chunks: &mut Vec<Chunk>,
571
19076
    node: &Node,
572
19076
    acquired_nodes: &mut AcquiredNodes<'_>,
573
19076
    cascaded: &CascadedValues<'_>,
574
19076
    layout_context: &LayoutContext,
575
19076
    dx: f64,
576
19076
    dy: f64,
577
19076
    depth: usize,
578
19076
    link: Option<String>,
579
19076
) {
580
19076
    let mut dx = dx;
581
19076
    let mut dy = dy;
582

            
583
21394
    for child in node.children() {
584
21394
        if child.is_chars() {
585
19703
            let values = cascaded.get();
586
19703
            child.borrow_chars().to_chunks(
587
19703
                &child,
588
19703
                Rc::new(values.clone()),
589
19703
                chunks,
590
19703
                dx,
591
19703
                dy,
592
19703
                depth,
593
19703
                link.clone(),
594
19703
            );
595
19703
        } else {
596
1691
            assert!(child.is_element());
597

            
598
1691
            match *child.borrow_element_data() {
599
1463
                ElementData::TSpan(ref tspan) => {
600
1463
                    let cascaded = CascadedValues::clone_with_node(cascaded, &child);
601
1463
                    tspan.to_chunks(
602
1463
                        &child,
603
1463
                        acquired_nodes,
604
1463
                        &cascaded,
605
1463
                        layout_context,
606
1463
                        chunks,
607
1463
                        dx,
608
1463
                        dy,
609
1463
                        depth + 1,
610
1463
                        link.clone(),
611
1463
                    );
612
1463
                }
613

            
614
152
                ElementData::Link(ref link) => {
615
152
                    // TSpan::default sets all offsets to 0,
616
152
                    // which is what we want in links.
617
152
                    //
618
152
                    // FIXME: This is the only place in the code where an element's method (TSpan::to_chunks)
619
152
                    // is called with a node that is not the element itself: here, `child` is a Link, not a TSpan.
620
152
                    //
621
152
                    // The code works because the `tspan` is dropped immediately after calling to_chunks and no
622
152
                    // references are retained for it.
623
152
                    let tspan = TSpan::default();
624
152
                    let cascaded = CascadedValues::clone_with_node(cascaded, &child);
625
152
                    tspan.to_chunks(
626
152
                        &child,
627
152
                        acquired_nodes,
628
152
                        &cascaded,
629
152
                        layout_context,
630
152
                        chunks,
631
152
                        dx,
632
152
                        dy,
633
152
                        depth + 1,
634
152
                        link.link.clone(),
635
152
                    );
636
152
                }
637

            
638
76
                ElementData::TRef(ref tref) => {
639
76
                    let cascaded = CascadedValues::clone_with_node(cascaded, &child);
640
76
                    tref.to_chunks(
641
76
                        &child,
642
76
                        acquired_nodes,
643
76
                        &cascaded,
644
76
                        chunks,
645
76
                        depth + 1,
646
76
                        layout_context,
647
76
                    );
648
76
                }
649

            
650
                _ => (),
651
            }
652
        }
653

            
654
        // After the first span, we don't need to carry over the parent's dx/dy.
655
21394
        dx = 0.0;
656
21394
        dy = 0.0;
657
    }
658
19076
}
659

            
660
/// In SVG text elements, we use `Chars` to store character data.  For example,
661
/// an element like `<text>Foo Bar</text>` will be a `Text` with a single child,
662
/// and the child will be a `Chars` with "Foo Bar" for its contents.
663
///
664
/// Text elements can contain `<tspan>` sub-elements.  In this case,
665
/// those `tspan` nodes will also contain `Chars` children.
666
///
667
/// A text or tspan element can contain more than one `Chars` child, for example,
668
/// if there is an XML comment that splits the character contents in two:
669
///
670
/// ```xml
671
/// <text>
672
///   This sentence will create a Chars.
673
///   <!-- this comment is ignored -->
674
///   This sentence will cretea another Chars.
675
/// </text>
676
/// ```
677
///
678
/// When rendering a text element, it will take care of concatenating the strings
679
/// in its `Chars` children as appropriate, depending on the
680
/// `xml:space="preserve"` attribute.  A `Chars` stores the characters verbatim
681
/// as they come out of the XML parser, after ensuring that they are valid UTF-8.
682

            
683
#[derive(Default)]
684
pub struct Chars {
685
    string: RefCell<String>,
686
    space_normalized: RefCell<Option<String>>,
687
}
688

            
689
impl Chars {
690
19636857
    pub fn new(initial_text: &str) -> Chars {
691
19636857
        Chars {
692
19636857
            string: RefCell::new(String::from(initial_text)),
693
19636857
            space_normalized: RefCell::new(None),
694
19636857
        }
695
19636857
    }
696

            
697
2
    pub fn is_empty(&self) -> bool {
698
2
        self.string.borrow().is_empty()
699
2
    }
700

            
701
47961
    pub fn append(&self, s: &str) {
702
47961
        self.string.borrow_mut().push_str(s);
703
47961
        *self.space_normalized.borrow_mut() = None;
704
47961
    }
705

            
706
19931
    fn ensure_normalized_string(&self, node: &Node, values: &ComputedValues) {
707
19931
        let mut normalized = self.space_normalized.borrow_mut();
708
19931

            
709
19931
        if (*normalized).is_none() {
710
19836
            let mode = match values.xml_space() {
711
19817
                XmlSpace::Default => XmlSpaceNormalize::Default(NormalizeDefault {
712
19817
                    has_element_before: node.previous_sibling().is_some(),
713
19817
                    has_element_after: node.next_sibling().is_some(),
714
19817
                }),
715

            
716
19
                XmlSpace::Preserve => XmlSpaceNormalize::Preserve,
717
            };
718

            
719
19836
            *normalized = Some(xml_space_normalize(mode, &self.string.borrow()));
720
95
        }
721
19931
    }
722

            
723
19931
    fn make_span(
724
19931
        &self,
725
19931
        node: &Node,
726
19931
        values: Rc<ComputedValues>,
727
19931
        dx: f64,
728
19931
        dy: f64,
729
19931
        depth: usize,
730
19931
        link_target: Option<String>,
731
19931
    ) -> Span {
732
19931
        self.ensure_normalized_string(node, &values);
733
19931

            
734
19931
        Span::new(
735
19931
            self.space_normalized.borrow().as_ref().unwrap(),
736
19931
            values,
737
19931
            dx,
738
19931
            dy,
739
19931
            depth,
740
19931
            link_target,
741
19931
        )
742
19931
    }
743

            
744
19931
    fn to_chunks(
745
19931
        &self,
746
19931
        node: &Node,
747
19931
        values: Rc<ComputedValues>,
748
19931
        chunks: &mut [Chunk],
749
19931
        dx: f64,
750
19931
        dy: f64,
751
19931
        depth: usize,
752
19931
        link_target: Option<String>,
753
19931
    ) {
754
19931
        let span = self.make_span(node, values, dx, dy, depth, link_target);
755
19931
        let num_chunks = chunks.len();
756
19931
        assert!(num_chunks > 0);
757

            
758
19931
        chunks[num_chunks - 1].spans.push(span);
759
19931
    }
760

            
761
829
    pub fn get_string(&self) -> String {
762
829
        self.string.borrow().clone()
763
829
    }
764
}
765

            
766
#[derive(Default)]
767
pub struct Text {
768
    x: Length<Horizontal>,
769
    y: Length<Vertical>,
770
    dx: Length<Horizontal>,
771
    dy: Length<Vertical>,
772
}
773

            
774
impl Text {
775
17556
    fn make_chunks(
776
17556
        &self,
777
17556
        node: &Node,
778
17556
        acquired_nodes: &mut AcquiredNodes<'_>,
779
17556
        cascaded: &CascadedValues<'_>,
780
17556
        layout_context: &LayoutContext,
781
17556
        x: f64,
782
17556
        y: f64,
783
17556
    ) -> Vec<Chunk> {
784
17556
        let mut chunks = Vec::new();
785
17556

            
786
17556
        let values = cascaded.get();
787
17556
        let params = NormalizeParams::new(values, &layout_context.viewport);
788
17556

            
789
17556
        chunks.push(Chunk::new(values, Some(x), Some(y)));
790
17556

            
791
17556
        let dx = self.dx.to_user(&params);
792
17556
        let dy = self.dy.to_user(&params);
793
17556

            
794
17556
        children_to_chunks(
795
17556
            &mut chunks,
796
17556
            node,
797
17556
            acquired_nodes,
798
17556
            cascaded,
799
17556
            layout_context,
800
17556
            dx,
801
17556
            dy,
802
17556
            0,
803
17556
            None,
804
17556
        );
805
17556
        chunks
806
17556
    }
807
}
808

            
809
// Parse an (optionally) comma-separated list and just return the first element.
810
//
811
// From https://gitlab.gnome.org/GNOME/librsvg/-/issues/183, the current implementation
812
// of text layout only supports a single value for the x/y/dx/dy attributes.  However,
813
// we need to be able to parse values with multiple lengths.  So, we'll do that, but just
814
// use the first value from each attribute.
815
33520
fn parse_list_and_extract_first<T: Copy + Default + Parse>(
816
33520
    dest: &mut T,
817
33520
    attr: QualName,
818
33520
    value: &str,
819
33520
    session: &Session,
820
33520
) {
821
33520
    let mut list: CommaSeparatedList<T, 0, 1024> = CommaSeparatedList(Vec::new());
822
33520

            
823
33520
    set_attribute(&mut list, attr.parse(value), session);
824
33520
    if list.0.is_empty() {
825
        *dest = Default::default();
826
33520
    } else {
827
33520
        *dest = list.0[0]; // ignore all but the first element
828
33520
    }
829
33520
}
830

            
831
impl ElementTrait for Text {
832
17919
    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
833
63186
        for (attr, value) in attrs.iter() {
834
63186
            match attr.expanded() {
835
                expanded_name!("", "x") => {
836
15392
                    parse_list_and_extract_first(&mut self.x, attr, value, session)
837
                }
838
                expanded_name!("", "y") => {
839
16703
                    parse_list_and_extract_first(&mut self.y, attr, value, session)
840
                }
841
                expanded_name!("", "dx") => {
842
19
                    parse_list_and_extract_first(&mut self.dx, attr, value, session)
843
                }
844
                expanded_name!("", "dy") => {
845
19
                    parse_list_and_extract_first(&mut self.dy, attr, value, session)
846
                }
847
31053
                _ => (),
848
            }
849
        }
850
17919
    }
851

            
852
17556
    fn layout(
853
17556
        &self,
854
17556
        node: &Node,
855
17556
        acquired_nodes: &mut AcquiredNodes<'_>,
856
17556
        cascaded: &CascadedValues<'_>,
857
17556
        viewport: &Viewport,
858
17556
        draw_ctx: &mut DrawingCtx,
859
17556
        _clipping: bool,
860
17556
    ) -> Result<Option<Layer>, InternalRenderingError> {
861
17556
        let values = cascaded.get();
862
17556
        let params = NormalizeParams::new(values, viewport);
863
17556

            
864
17556
        let elt = node.borrow_element();
865
17556

            
866
17556
        let session = draw_ctx.session().clone();
867
17556

            
868
17556
        let stacking_ctx = StackingContext::new(
869
17556
            &session,
870
17556
            acquired_nodes,
871
17556
            &elt,
872
17556
            values.transform(),
873
17556
            None,
874
17556
            values,
875
17556
        );
876

            
877
17556
        let layout_text = {
878
17556
            let layout_context = LayoutContext {
879
17556
                writing_mode: values.writing_mode(),
880
17556
                font_options: draw_ctx.get_font_options(),
881
17556
                viewport: *viewport,
882
17556
                session: session.clone(),
883
17556
            };
884
17556

            
885
17556
            let mut x = self.x.to_user(&params);
886
17556
            let mut y = self.y.to_user(&params);
887
17556

            
888
17556
            let chunks = self.make_chunks(node, acquired_nodes, cascaded, &layout_context, x, y);
889
17556

            
890
17556
            let mut measured_chunks = Vec::new();
891
35739
            for chunk in &chunks {
892
18183
                measured_chunks.push(MeasuredChunk::from_chunk(&layout_context, chunk));
893
18183
            }
894

            
895
17556
            let mut positioned_chunks = Vec::new();
896
35739
            for chunk in &measured_chunks {
897
18183
                let chunk_x = chunk.x.unwrap_or(x);
898
18183
                let chunk_y = chunk.y.unwrap_or(y);
899
18183

            
900
18183
                let positioned =
901
18183
                    PositionedChunk::from_measured(&layout_context, chunk, chunk_x, chunk_y);
902
18183

            
903
18183
                x = positioned.next_chunk_x;
904
18183
                y = positioned.next_chunk_y;
905
18183

            
906
18183
                positioned_chunks.push(positioned);
907
18183
            }
908

            
909
17556
            let mut layout_spans = Vec::new();
910
35739
            for chunk in &positioned_chunks {
911
38076
                for span in &chunk.spans {
912
19893
                    layout_spans.push(span.layout(&layout_context, acquired_nodes));
913
19893
                }
914
            }
915

            
916
17556
            let text_extents: Option<Rect> = layout_spans
917
17556
                .iter()
918
19893
                .map(|span| span.extents)
919
17556
                .reduce(|a, b| match (a, b) {
920
19
                    (None, None) => None,
921
285
                    (None, Some(b)) => Some(b),
922
817
                    (Some(a), None) => Some(a),
923
1273
                    (Some(a), Some(b)) => Some(a.union(&b)),
924
17556
                })
925
17556
                .flatten();
926
17556

            
927
17556
            let mut text_spans = Vec::new();
928
37449
            for span in layout_spans {
929
19893
                let normalize_values = NormalizeValues::new(&span.values);
930
19893

            
931
19893
                let stroke_paint = span.stroke_paint.to_user_space(
932
19893
                    &text_extents,
933
19893
                    &layout_context.viewport,
934
19893
                    &normalize_values,
935
19893
                );
936
19893
                let fill_paint = span.fill_paint.to_user_space(
937
19893
                    &text_extents,
938
19893
                    &layout_context.viewport,
939
19893
                    &normalize_values,
940
19893
                );
941
19893

            
942
19893
                let text_span = TextSpan {
943
19893
                    layout: span.layout,
944
19893
                    gravity: span.gravity,
945
19893
                    extents: span.extents,
946
19893
                    is_visible: span.is_visible,
947
19893
                    x: span.x,
948
19893
                    y: span.y,
949
19893
                    paint_order: span.paint_order,
950
19893
                    stroke: span.stroke,
951
19893
                    stroke_paint,
952
19893
                    fill_paint,
953
19893
                    text_rendering: span.text_rendering,
954
19893
                    link_target: span.link_target,
955
19893
                };
956
19893

            
957
19893
                text_spans.push(text_span);
958
19893
            }
959

            
960
17556
            layout::Text {
961
17556
                spans: text_spans,
962
17556
                extents: text_extents,
963
17556
            }
964
17556
        };
965
17556

            
966
17556
        Ok(Some(Layer {
967
17556
            kind: LayerKind::Text(Box::new(layout_text)),
968
17556
            stacking_ctx,
969
17556
        }))
970
17556
    }
971

            
972
17556
    fn draw(
973
17556
        &self,
974
17556
        node: &Node,
975
17556
        acquired_nodes: &mut AcquiredNodes<'_>,
976
17556
        cascaded: &CascadedValues<'_>,
977
17556
        viewport: &Viewport,
978
17556
        draw_ctx: &mut DrawingCtx,
979
17556
        clipping: bool,
980
17556
    ) -> Result<BoundingBox, InternalRenderingError> {
981
17556
        self.layout(node, acquired_nodes, cascaded, viewport, draw_ctx, clipping)
982
17556
            .and_then(|layer| {
983
17556
                draw_ctx.draw_layer(layer.as_ref().unwrap(), acquired_nodes, clipping, viewport)
984
17556
            })
985
17556
    }
986
}
987

            
988
#[derive(Default)]
989
pub struct TRef {
990
    link: Option<NodeId>,
991
}
992

            
993
impl TRef {
994
76
    fn to_chunks(
995
76
        &self,
996
76
        node: &Node,
997
76
        acquired_nodes: &mut AcquiredNodes<'_>,
998
76
        cascaded: &CascadedValues<'_>,
999
76
        chunks: &mut Vec<Chunk>,
76
        depth: usize,
76
        layout_context: &LayoutContext,
76
    ) {
76
        if self.link.is_none() {
            return;
76
        }
76

            
76
        let link = self.link.as_ref().unwrap();
76

            
76
        let values = cascaded.get();
76
        if !values.is_displayed() {
            return;
76
        }
76
        if let Ok(acquired) = acquired_nodes.acquire(link) {
76
            let c = acquired.get();
76
            extract_chars_children_to_chunks_recursively(chunks, c, Rc::new(values.clone()), depth);
76
        } else {
            rsvg_log!(
                layout_context.session,
                "element {} references a nonexistent text source \"{}\"",
                node,
                link,
            );
        }
76
    }
}
228
fn extract_chars_children_to_chunks_recursively(
228
    chunks: &mut Vec<Chunk>,
228
    node: &Node,
228
    values: Rc<ComputedValues>,
228
    depth: usize,
228
) {
380
    for child in node.children() {
380
        let values = values.clone();
380

            
380
        if child.is_chars() {
228
            child
228
                .borrow_chars()
228
                .to_chunks(&child, values, chunks, 0.0, 0.0, depth, None)
        } else {
152
            extract_chars_children_to_chunks_recursively(chunks, &child, values, depth + 1)
        }
    }
228
}
impl ElementTrait for TRef {
76
    fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) {
76
        self.link = attrs
76
            .iter()
76
            .find(|(attr, _)| attr.expanded() == expanded_name!(xlink "href"))
76
            // Unlike other elements which use `href` in SVG2 versus `xlink:href` in SVG1.1,
76
            // the <tref> element got removed in SVG2.  So, here we still use a match
76
            // against the full namespaced version of the attribute.
76
            .and_then(|(attr, value)| NodeId::parse(value).attribute(attr).ok());
76
    }
}
#[derive(Default)]
pub struct TSpan {
    x: Option<Length<Horizontal>>,
    y: Option<Length<Vertical>>,
    dx: Length<Horizontal>,
    dy: Length<Vertical>,
}
impl TSpan {
1615
    fn to_chunks(
1615
        &self,
1615
        node: &Node,
1615
        acquired_nodes: &mut AcquiredNodes<'_>,
1615
        cascaded: &CascadedValues<'_>,
1615
        layout_context: &LayoutContext,
1615
        chunks: &mut Vec<Chunk>,
1615
        dx: f64,
1615
        dy: f64,
1615
        depth: usize,
1615
        link: Option<String>,
1615
    ) {
1615
        let values = cascaded.get();
1615
        if !values.is_displayed() {
95
            return;
1520
        }
1520

            
1520
        let params = NormalizeParams::new(values, &layout_context.viewport);
1520

            
1520
        let x = self.x.map(|l| l.to_user(&params));
1520
        let y = self.y.map(|l| l.to_user(&params));
1520

            
1520
        let span_dx = dx + self.dx.to_user(&params);
1520
        let span_dy = dy + self.dy.to_user(&params);
1520

            
1520
        if x.is_some() || y.is_some() {
627
            chunks.push(Chunk::new(values, x, y));
893
        }
1520
        children_to_chunks(
1520
            chunks,
1520
            node,
1520
            acquired_nodes,
1520
            cascaded,
1520
            layout_context,
1520
            span_dx,
1520
            span_dy,
1520
            depth,
1520
            link,
1520
        );
1615
    }
}
impl ElementTrait for TSpan {
1624
    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
2370
        for (attr, value) in attrs.iter() {
2370
            match attr.expanded() {
                expanded_name!("", "x") => {
627
                    parse_list_and_extract_first(&mut self.x, attr, value, session)
                }
                expanded_name!("", "y") => {
418
                    parse_list_and_extract_first(&mut self.y, attr, value, session)
                }
                expanded_name!("", "dx") => {
57
                    parse_list_and_extract_first(&mut self.dx, attr, value, session)
                }
                expanded_name!("", "dy") => {
285
                    parse_list_and_extract_first(&mut self.dy, attr, value, session)
                }
983
                _ => (),
            }
        }
1624
    }
}
impl From<FontStyle> for pango::Style {
19893
    fn from(s: FontStyle) -> pango::Style {
19893
        match s {
19551
            FontStyle::Normal => pango::Style::Normal,
342
            FontStyle::Italic => pango::Style::Italic,
            FontStyle::Oblique => pango::Style::Oblique,
        }
19893
    }
}
impl From<FontVariant> for pango::Variant {
19893
    fn from(v: FontVariant) -> pango::Variant {
19893
        match v {
19874
            FontVariant::Normal => pango::Variant::Normal,
19
            FontVariant::SmallCaps => pango::Variant::SmallCaps,
        }
19893
    }
}
impl From<FontStretch> for pango::Stretch {
19893
    fn from(s: FontStretch) -> pango::Stretch {
19893
        match s {
19893
            FontStretch::Normal => pango::Stretch::Normal,
            FontStretch::Wider => pango::Stretch::Expanded, // not quite correct
            FontStretch::Narrower => pango::Stretch::Condensed, // not quite correct
            FontStretch::UltraCondensed => pango::Stretch::UltraCondensed,
            FontStretch::ExtraCondensed => pango::Stretch::ExtraCondensed,
            FontStretch::Condensed => pango::Stretch::Condensed,
            FontStretch::SemiCondensed => pango::Stretch::SemiCondensed,
            FontStretch::SemiExpanded => pango::Stretch::SemiExpanded,
            FontStretch::Expanded => pango::Stretch::Expanded,
            FontStretch::ExtraExpanded => pango::Stretch::ExtraExpanded,
            FontStretch::UltraExpanded => pango::Stretch::UltraExpanded,
        }
19893
    }
}
impl From<FontWeight> for pango::Weight {
19893
    fn from(w: FontWeight) -> pango::Weight {
19893
        pango::Weight::__Unknown(w.numeric_weight().into())
19893
    }
}
impl From<Direction> for pango::Direction {
133
    fn from(d: Direction) -> pango::Direction {
133
        match d {
            Direction::Ltr => pango::Direction::Ltr,
133
            Direction::Rtl => pango::Direction::Rtl,
        }
133
    }
}
impl From<WritingMode> for pango::Direction {
19798
    fn from(m: WritingMode) -> pango::Direction {
        use WritingMode::*;
19798
        match m {
19798
            HorizontalTb | VerticalRl | VerticalLr | LrTb | Lr | Tb | TbRl => pango::Direction::Ltr,
            RlTb | Rl => pango::Direction::Rtl,
        }
19798
    }
}
impl From<WritingMode> for pango::Gravity {
19931
    fn from(m: WritingMode) -> pango::Gravity {
        use WritingMode::*;
19931
        match m {
19931
            HorizontalTb | LrTb | Lr | RlTb | Rl => pango::Gravity::South,
            VerticalRl | Tb | TbRl => pango::Gravity::East,
            VerticalLr => pango::Gravity::West,
        }
19931
    }
}
/// Constants with Unicode's directional formatting characters
///
/// <https://unicode.org/reports/tr9/#Directional_Formatting_Characters>
pub mod directional_formatting_characters {
    /// Left-to-Right Embedding
    ///
    /// Treat the following text as embedded left-to-right.
    pub const LRE: char = '\u{202a}';
    /// Right-to-Left Embedding
    ///
    /// Treat the following text as embedded right-to-left.
    pub const RLE: char = '\u{202b}';
    /// Left-to-Right Override
    ///
    /// Force following characters to be treated as strong left-to-right characters.
    pub const LRO: char = '\u{202d}';
    /// Right-to-Left Override
    ///
    /// Force following characters to be treated as strong right-to-left characters.
    pub const RLO: char = '\u{202e}';
    /// Pop Directional Formatting
    ///
    /// End the scope of the last LRE, RLE, RLO, or LRO.
    pub const PDF: char = '\u{202c}';
    /// Left-to-Right Isolate
    ///
    /// Treat the following text as isolated and left-to-right.
    pub const LRI: char = '\u{2066}';
    /// Right-to-Left Isolate
    ///
    /// Treat the following text as isolated and right-to-left.
    pub const RLI: char = '\u{2067}';
    /// First Strong Isolate
    ///
    /// Treat the following text as isolated and in the direction of its first strong
    /// directional character that is not inside a nested isolate.
    pub const FSI: char = '\u{2068}';
    /// Pop Directional Isolate
    ///
    /// End the scope of the last LRI, RLI, or FSI.
    pub const PDI: char = '\u{2069}';
}
/// Unicode control characters to be inserted when `unicode-bidi` is specified.
///
/// The `unicode-bidi` property is used to change the embedding of a text span within
/// another.  This struct contains slices with the control characters that must be
/// inserted into the text stream at the span's limits so that the bidi/shaping engine
/// will know what to do.
pub struct BidiControl {
    pub start: &'static [char],
    pub end: &'static [char],
}
impl BidiControl {
    /// Creates a `BidiControl` from the properties that determine it.
    ///
    /// See the table titled "Bidi control codes injected..." in
    /// <https://www.w3.org/TR/css-writing-modes-3/#unicode-bidi>
    #[rustfmt::skip]
19969
    pub fn from_unicode_bidi_and_direction(unicode_bidi: UnicodeBidi, direction: Direction) -> BidiControl {
        use UnicodeBidi::*;
        use Direction::*;
        use directional_formatting_characters::*;
19969
        let (start, end) = match (unicode_bidi, direction) {
19940
            (Normal,          _)   => (&[][..],         &[][..]),
            (Embed,           Ltr) => (&[LRE][..],      &[PDF][..]),
6
            (Embed,           Rtl) => (&[RLE][..],      &[PDF][..]),
            (Isolate,         Ltr) => (&[LRI][..],      &[PDI][..]),
2
            (Isolate,         Rtl) => (&[RLI][..],      &[PDI][..]),
            (BidiOverride,    Ltr) => (&[LRO][..],      &[PDF][..]),
19
            (BidiOverride,    Rtl) => (&[RLO][..],      &[PDF][..]),
2
            (IsolateOverride, Ltr) => (&[FSI, LRO][..], &[PDF, PDI][..]),
            (IsolateOverride, Rtl) => (&[FSI, RLO][..], &[PDF, PDI][..]),
            (Plaintext,       Ltr) => (&[FSI][..],      &[PDI][..]),
            (Plaintext,       Rtl) => (&[FSI][..],      &[PDI][..]),
        };
19969
        BidiControl { start, end }
19969
    }
}
/// Prepends and appends Unicode directional formatting characters.
19931
fn wrap_with_direction_control_chars(s: &str, bidi_control: &BidiControl) -> String {
19931
    let mut res =
19931
        String::with_capacity(s.len() + bidi_control.start.len() + bidi_control.end.len());
19950
    for &ch in bidi_control.start {
19
        res.push(ch);
19
    }
19931
    res.push_str(s);
19950
    for &ch in bidi_control.end {
19
        res.push(ch);
19
    }
19931
    res
19931
}
/// Returns `None` if the layout would be invalid due to, for example, out-of-bounds font sizes.
19931
fn create_pango_layout(
19931
    layout_context: &LayoutContext,
19931
    props: &FontProperties,
19931
    text: &str,
19931
) -> Option<pango::Layout> {
19931
    let pango_context = create_pango_context(&layout_context.font_options);
19931
    if let XmlLang(Some(ref lang)) = props.xml_lang {
247
        pango_context.set_language(Some(&pango::Language::from_string(lang.as_str())));
19684
    }
19931
    pango_context.set_base_gravity(pango::Gravity::from(layout_context.writing_mode));
19931

            
19931
    match (props.unicode_bidi, props.direction) {
19
        (UnicodeBidi::BidiOverride, _) | (UnicodeBidi::Embed, _) => {
19
            pango_context.set_base_dir(pango::Direction::from(props.direction));
19
        }
19912
        (_, direction) if direction != Direction::Ltr => {
114
            pango_context.set_base_dir(pango::Direction::from(direction));
114
        }
19798
        (_, _) => {
19798
            pango_context.set_base_dir(pango::Direction::from(layout_context.writing_mode));
19798
        }
    }
19931
    let layout = pango::Layout::new(&pango_context);
19931

            
19931
    let font_size = PangoUnits::from_pixels(props.font_size);
19931
    let letter_spacing = PangoUnits::from_pixels(props.letter_spacing);
19931

            
19931
    if font_size.is_none() {
19
        rsvg_log!(
19
            &layout_context.session,
19
            "font-size {} is out of bounds; ignoring span",
19
            props.font_size
19
        );
19912
    }
19931
    if letter_spacing.is_none() {
19
        rsvg_log!(
19
            &layout_context.session,
19
            "letter-spacing {} is out of bounds; ignoring span",
19
            props.letter_spacing
19
        );
19912
    }
19931
    if let (Some(font_size), Some(letter_spacing)) = (font_size, letter_spacing) {
19893
        let attr_list = pango::AttrList::new();
19893
        add_pango_attributes(&attr_list, props, 0, text.len(), font_size, letter_spacing);
19893

            
19893
        layout.set_attributes(Some(&attr_list));
19893
        layout.set_text(text);
19893
        layout.set_auto_dir(false);
19893

            
19893
        Some(layout)
    } else {
38
        None
    }
19931
}
/// Adds Pango attributes, suitable for a span of text, to an `AttrList`.
19893
fn add_pango_attributes(
19893
    attr_list: &pango::AttrList,
19893
    props: &FontProperties,
19893
    start_index: usize,
19893
    end_index: usize,
19893
    font_size: PangoUnits,
19893
    letter_spacing: PangoUnits,
19893
) {
19893
    let start_index = u32::try_from(start_index).expect("Pango attribute index must fit in u32");
19893
    let end_index = u32::try_from(end_index).expect("Pango attribute index must fit in u32");
19893
    assert!(start_index <= end_index);
19893
    let mut attributes = Vec::new();
19893

            
19893
    let mut font_desc = pango::FontDescription::new();
19893
    font_desc.set_family(props.font_family.as_str());
19893
    font_desc.set_style(pango::Style::from(props.font_style));
19893

            
19893
    font_desc.set_variant(pango::Variant::from(props.font_variant));
19893

            
19893
    font_desc.set_weight(pango::Weight::from(props.font_weight));
19893
    font_desc.set_stretch(pango::Stretch::from(props.font_stretch));
19893

            
19893
    font_desc.set_size(font_size.0);
19893

            
19893
    attributes.push(pango::AttrFontDesc::new(&font_desc).upcast());
19893

            
19893
    attributes.push(pango::AttrInt::new_letter_spacing(letter_spacing.0).upcast());
19893

            
19893
    if props.text_decoration.overline {
        attributes.push(pango::AttrInt::new_overline(pango::Overline::Single).upcast());
19893
    }
19893
    if props.text_decoration.underline {
38
        attributes.push(pango::AttrInt::new_underline(pango::Underline::Single).upcast());
19855
    }
19893
    if props.text_decoration.strike {
19
        attributes.push(pango::AttrInt::new_strikethrough(true).upcast());
19874
    }
    // Set the range in each attribute
59736
    for attr in &mut attributes {
39843
        attr.set_start_index(start_index);
39843
        attr.set_end_index(end_index);
39843
    }
    // Add the attributes to the attr_list
59736
    for attr in attributes {
39843
        attr_list.insert(attr);
39843
    }
19893
}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
1
    fn chars_default() {
1
        let c = Chars::default();
1
        assert!(c.is_empty());
1
        assert!(c.space_normalized.borrow().is_none());
1
    }
    #[test]
1
    fn chars_new() {
1
        let example = "Test 123";
1
        let c = Chars::new(example);
1
        assert_eq!(c.get_string(), example);
1
        assert!(c.space_normalized.borrow().is_none());
1
    }
    // This is called _horizontal because the property value in "CSS Writing Modes 3"
    // is `horizontal-tb`.  Eventually we will support that and this will make more sense.
    #[test]
1
    fn adjusted_advance_horizontal_ltr() {
        use Direction::*;
        use TextAnchor::*;
1
        assert_eq!(
1
            text_anchor_offset(
1
                Start,
1
                Ltr,
1
                WritingMode::Lr,
1
                Rect::from_size(1.0, 2.0).translate((5.0, 6.0))
1
            ),
1
            (-5.0, 0.0)
1
        );
1
        assert_eq!(
1
            text_anchor_offset(
1
                Middle,
1
                Ltr,
1
                WritingMode::Lr,
1
                Rect::from_size(1.0, 2.0).translate((5.0, 6.0))
1
            ),
1
            (-5.5, 0.0)
1
        );
1
        assert_eq!(
1
            text_anchor_offset(
1
                End,
1
                Ltr,
1
                WritingMode::Lr,
1
                Rect::from_size(1.0, 2.0).translate((5.0, 6.0))
1
            ),
1
            (-6.0, 0.0)
1
        );
1
    }
    #[test]
1
    fn adjusted_advance_horizontal_rtl() {
        use Direction::*;
        use TextAnchor::*;
1
        assert_eq!(
1
            text_anchor_offset(
1
                Start,
1
                Rtl,
1
                WritingMode::Rl,
1
                Rect::from_size(1.0, 2.0).translate((5.0, 6.0))
1
            ),
1
            (-6.0, 0.0)
1
        );
1
        assert_eq!(
1
            text_anchor_offset(
1
                Middle,
1
                Rtl,
1
                WritingMode::Rl,
1
                Rect::from_size(1.0, 2.0).translate((5.0, 6.0))
1
            ),
1
            (-5.5, 0.0)
1
        );
1
        assert_eq!(
1
            text_anchor_offset(
1
                TextAnchor::End,
1
                Direction::Rtl,
1
                WritingMode::Rl,
1
                Rect::from_size(1.0, 2.0).translate((5.0, 6.0))
1
            ),
1
            (-5.0, 0.0)
1
        );
1
    }
    // This is called _vertical because "CSS Writing Modes 3" has both `vertical-rl` (East
    // Asia), and `vertical-lr` (Manchu, Mongolian), but librsvg does not support block
    // flow direction properly yet.  Eventually we will support that and this will make
    // more sense.
    #[test]
1
    fn adjusted_advance_vertical() {
        use Direction::*;
        use TextAnchor::*;
1
        assert_eq!(
1
            text_anchor_offset(Start, Ltr, WritingMode::Tb, Rect::from_size(2.0, 4.0)),
1
            (0.0, 0.0)
1
        );
1
        assert_eq!(
1
            text_anchor_offset(Middle, Ltr, WritingMode::Tb, Rect::from_size(2.0, 4.0)),
1
            (0.0, -2.0)
1
        );
1
        assert_eq!(
1
            text_anchor_offset(End, Ltr, WritingMode::Tb, Rect::from_size(2.0, 4.0)),
1
            (0.0, -4.0)
1
        );
1
    }
    #[test]
1
    fn pango_units_works() {
1
        assert_eq!(PangoUnits::from_pixels(10.0).unwrap().0, pango::SCALE * 10);
1
    }
    #[test]
1
    fn pango_units_detects_overflow() {
1
        assert!(PangoUnits::from_pixels(1e7).is_none());
1
    }
}