From e9d02f73896ced2db635fd3d35fc9be26fdf144a Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 25 Oct 2021 12:06:44 -0500 Subject: [PATCH 01/17] Rename UnicodeBidi::Override to UnicodeBidi::BidiOverride The next commit will introduce IsolateOverride, so we don't want ambiguity. Part-of: --- src/property_defs.rs | 2 +- src/text.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/property_defs.rs b/src/property_defs.rs index e9cd1352a..d88454ed8 100644 --- a/src/property_defs.rs +++ b/src/property_defs.rs @@ -1022,7 +1022,7 @@ make_property!( identifiers: "normal" => Normal, "embed" => Embed, - "bidi-override" => Override, + "bidi-override" => BidiOverride, ); make_property!( diff --git a/src/text.rs b/src/text.rs index 1c0c11a7e..2606e890d 100644 --- a/src/text.rs +++ b/src/text.rs @@ -1063,7 +1063,7 @@ fn create_pango_layout(draw_ctx: &DrawingCtx, props: &FontProperties, text: &str pango_context.set_base_gravity(pango::Gravity::from(props.writing_mode)); match (props.unicode_bidi, props.direction) { - (UnicodeBidi::Override, _) | (UnicodeBidi::Embed, _) => { + (UnicodeBidi::BidiOverride, _) | (UnicodeBidi::Embed, _) => { pango_context.set_base_dir(pango::Direction::from(props.direction)); } -- GitLab From d992bb825a443e29738b0034448c64e88dd3595f Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 25 Oct 2021 12:15:12 -0500 Subject: [PATCH 02/17] Add all the CSS3 identifiers to unicode-bidi Part-of: --- src/property_defs.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/property_defs.rs b/src/property_defs.rs index d88454ed8..76f9924c4 100644 --- a/src/property_defs.rs +++ b/src/property_defs.rs @@ -1022,7 +1022,10 @@ make_property!( identifiers: "normal" => Normal, "embed" => Embed, + "isolate" => Isolate, "bidi-override" => BidiOverride, + "isolate-override" => IsolateOverride, + "plaintext" => Plaintext, ); make_property!( -- GitLab From af81749b524585a45ac2fc55647931e640c40ad8 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 25 Oct 2021 13:19:51 -0500 Subject: [PATCH 03/17] BidiControl: generate the Unicode control characters for unicode-bidi This is just a lookup table based on the (direction, unicode-bidi) properties. The next step is to actually use this information when processing each span. Part-of: --- src/text.rs | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/src/text.rs b/src/text.rs index 2606e890d..afa24360c 100644 --- a/src/text.rs +++ b/src/text.rs @@ -1053,6 +1053,97 @@ impl From for pango::Gravity { } } +/// Constants with Unicode's directional formatting characters +/// +/// https://unicode.org/reports/tr9/#Directional_Formatting_Characters +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. +struct BidiControl { + start: &'static [char], + 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] + fn from_unicode_bidi_and_direction(unicode_bidi: UnicodeBidi, direction: Direction) -> BidiControl { + use UnicodeBidi::*; + use Direction::*; + use directional_formatting_characters::*; + + let (start, end) = match (unicode_bidi, direction) { + (Normal, _) => (&[][..], &[][..]), + (Embed, Ltr) => (&[LRE][..], &[PDF][..]), + (Embed, Rtl) => (&[RLE][..], &[PDF][..]), + (Isolate, Ltr) => (&[LRI][..], &[PDI][..]), + (Isolate, Rtl) => (&[RLI][..], &[PDI][..]), + (BidiOverride, Ltr) => (&[LRO][..], &[PDF][..]), + (BidiOverride, Rtl) => (&[RLO][..], &[PDF][..]), + (IsolateOverride, Ltr) => (&[FSI, LRO][..], &[PDF, PDI][..]), + (IsolateOverride, Rtl) => (&[FSI, RLO][..], &[PDF, PDI][..]), + (Plaintext, Ltr) => (&[FSI][..], &[PDI][..]), + (Plaintext, Rtl) => (&[FSI][..], &[PDI][..]), + }; + + BidiControl { start, end } + } +} + fn create_pango_layout(draw_ctx: &DrawingCtx, props: &FontProperties, text: &str) -> pango::Layout { let pango_context = draw_ctx.create_pango_context(); -- GitLab From bb2c019f5c15d30e74faa150dca0f68da01d6633 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 25 Oct 2021 17:53:45 -0500 Subject: [PATCH 04/17] (#249): Basic implementation of the unicode-bidi property This commit causes each span's text to be wrapped with the directional control characters specified in https://www.w3.org/TR/css-writing-modes-3/#unicode-bidi For a more complete implementation of unicode-bidi, we'll have to change the code to actually bidi/shape the whole character content of a element and its children, not just each individual span. That will come soon. Part-of: --- src/text.rs | 27 ++++++++++++++++++- .../text/unicode-bidi-override-ref.svg | 6 +++++ tests/fixtures/text/unicode-bidi-override.svg | 6 +++++ tests/src/text.rs | 6 +++++ 4 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/text/unicode-bidi-override-ref.svg create mode 100644 tests/fixtures/text/unicode-bidi-override.svg diff --git a/src/text.rs b/src/text.rs index afa24360c..00258defd 100644 --- a/src/text.rs +++ b/src/text.rs @@ -335,7 +335,14 @@ impl MeasuredSpan { let params = NormalizeParams::new(&values, &view_params); let properties = FontProperties::new(&values, text_writing_mode, ¶ms); - let layout = create_pango_layout(draw_ctx, &properties, &span.text); + + let bidi_control = BidiControl::from_unicode_bidi_and_direction( + properties.unicode_bidi, + properties.direction, + ); + + let with_control_chars = wrap_with_direction_control_chars(&span.text, &bidi_control); + let layout = create_pango_layout(draw_ctx, &properties, &with_control_chars); let (w, h) = layout.size(); let w = f64::from(w) / f64::from(pango::SCALE); @@ -1144,6 +1151,24 @@ impl BidiControl { } } +/// Prepends and appends Unicode directional formatting characters. +fn wrap_with_direction_control_chars(s: &str, bidi_control: &BidiControl) -> String { + let mut res = + String::with_capacity(s.len() + bidi_control.start.len() + bidi_control.end.len()); + + for &ch in bidi_control.start { + res.push(ch); + } + + res.push_str(s); + + for &ch in bidi_control.end { + res.push(ch); + } + + res +} + fn create_pango_layout(draw_ctx: &DrawingCtx, props: &FontProperties, text: &str) -> pango::Layout { let pango_context = draw_ctx.create_pango_context(); diff --git a/tests/fixtures/text/unicode-bidi-override-ref.svg b/tests/fixtures/text/unicode-bidi-override-ref.svg new file mode 100644 index 000000000..2357471bb --- /dev/null +++ b/tests/fixtures/text/unicode-bidi-override-ref.svg @@ -0,0 +1,6 @@ + + + + + ÉAppAÉÉAp + diff --git a/tests/fixtures/text/unicode-bidi-override.svg b/tests/fixtures/text/unicode-bidi-override.svg new file mode 100644 index 000000000..5b230d261 --- /dev/null +++ b/tests/fixtures/text/unicode-bidi-override.svg @@ -0,0 +1,6 @@ + + + + + ÉApÉApÉAp + diff --git a/tests/src/text.rs b/tests/src/text.rs index cc3c1527e..779fb1274 100644 --- a/tests/src/text.rs +++ b/tests/src/text.rs @@ -39,3 +39,9 @@ test_svg_reference!( "tests/fixtures/text/span-bounds-when-offset-by-dx.svg", "tests/fixtures/text/span-bounds-when-offset-by-dx-ref.svg" ); + +test_svg_reference!( + unicode_bidi_override, + "tests/fixtures/text/unicode-bidi-override.svg", + "tests/fixtures/text/unicode-bidi-override-ref.svg" +); -- GitLab From 7fdc264973990f2785ba4f653300abd1875af144 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 25 Oct 2021 18:17:28 -0500 Subject: [PATCH 05/17] Set a pango::Layout's font description as an attribute Instead of using layout.set_font_descrption(). This is in preparation of generating a long string out of all the character content in a element's children, and setting Pango attributes with ranges within that string. Part-of: --- src/text.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/text.rs b/src/text.rs index 00258defd..29253090c 100644 --- a/src/text.rs +++ b/src/text.rs @@ -1192,22 +1192,8 @@ fn create_pango_layout(draw_ctx: &DrawingCtx, props: &FontProperties, text: &str } } - let mut font_desc = pango_context.font_description().unwrap(); - font_desc.set_family(props.font_family.as_str()); - font_desc.set_style(pango::Style::from(props.font_style)); - - // PANGO_VARIANT_SMALL_CAPS does nothing: https://gitlab.gnome.org/GNOME/pango/-/issues/566 - // see below for using the "smcp" OpenType feature for fonts that support it. - // font_desc.set_variant(pango::Variant::from(props.font_variant)); - - font_desc.set_weight(pango::Weight::from(props.font_weight)); - font_desc.set_stretch(pango::Stretch::from(props.font_stretch)); - - font_desc.set_size(to_pango_units(props.font_size)); - let layout = pango::Layout::new(&pango_context); layout.set_auto_dir(false); - layout.set_font_description(Some(&font_desc)); // FIXME: For now we ignore the `line-height` property, even though we parse it. // We would need to do something like this: @@ -1223,6 +1209,20 @@ fn create_pango_layout(draw_ctx: &DrawingCtx, props: &FontProperties, text: &str let attr_list = pango::AttrList::new(); + let mut font_desc = pango_context.font_description().unwrap(); + font_desc.set_family(props.font_family.as_str()); + font_desc.set_style(pango::Style::from(props.font_style)); + + // PANGO_VARIANT_SMALL_CAPS does nothing: https://gitlab.gnome.org/GNOME/pango/-/issues/566 + // see below for using the "smcp" OpenType feature for fonts that support it. + // font_desc.set_variant(pango::Variant::from(props.font_variant)); + + font_desc.set_weight(pango::Weight::from(props.font_weight)); + font_desc.set_stretch(pango::Stretch::from(props.font_stretch)); + + font_desc.set_size(to_pango_units(props.font_size)); + attr_list.insert(pango::Attribute::new_font_desc(&font_desc)); + attr_list.insert(pango::Attribute::new_letter_spacing(to_pango_units( props.letter_spacing, ))); -- GitLab From 6f9a5a4aaf7451e16a5293709ba55567e78e1c95 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 25 Oct 2021 18:20:20 -0500 Subject: [PATCH 06/17] Do not set the PangoAlignment of a layout This is not necessary since librsvg doesn't use Pango's multi-line layouts; it does not use Pango's features to wrap text to a certain width. Part-of: --- src/text.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/text.rs b/src/text.rs index 29253090c..1637ff900 100644 --- a/src/text.rs +++ b/src/text.rs @@ -1030,15 +1030,6 @@ impl From for pango::Direction { } } -impl From for pango::Alignment { - fn from(d: Direction) -> pango::Alignment { - match d { - Direction::Ltr => pango::Alignment::Left, - Direction::Rtl => pango::Alignment::Right, - } - } -} - impl From for pango::Direction { fn from(m: WritingMode) -> pango::Direction { use WritingMode::*; @@ -1243,7 +1234,6 @@ fn create_pango_layout(draw_ctx: &DrawingCtx, props: &FontProperties, text: &str } layout.set_attributes(Some(&attr_list)); - layout.set_alignment(pango::Alignment::from(props.direction)); layout.set_text(text); layout -- GitLab From 26c4e2547e2c6a8f7c8437c648b6fb9657f53d67 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 25 Oct 2021 18:22:59 -0500 Subject: [PATCH 07/17] Gather all the pango::Layout mutation in a single place Part-of: --- src/text.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/text.rs b/src/text.rs index 1637ff900..608cc0772 100644 --- a/src/text.rs +++ b/src/text.rs @@ -1184,7 +1184,6 @@ fn create_pango_layout(draw_ctx: &DrawingCtx, props: &FontProperties, text: &str } let layout = pango::Layout::new(&pango_context); - layout.set_auto_dir(false); // FIXME: For now we ignore the `line-height` property, even though we parse it. // We would need to do something like this: @@ -1235,6 +1234,7 @@ fn create_pango_layout(draw_ctx: &DrawingCtx, props: &FontProperties, text: &str layout.set_attributes(Some(&attr_list)); layout.set_text(text); + layout.set_auto_dir(false); layout } -- GitLab From 29e0e87544969f6699d1dd912044a35aff8c3116 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 25 Oct 2021 18:31:25 -0500 Subject: [PATCH 08/17] Extract function to populate a pango::AttrList from FontProperties Part-of: --- src/text.rs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/text.rs b/src/text.rs index 608cc0772..eb276a77e 100644 --- a/src/text.rs +++ b/src/text.rs @@ -1198,8 +1198,22 @@ fn create_pango_layout(draw_ctx: &DrawingCtx, props: &FontProperties, text: &str // Maybe we need to implement layout of individual lines by hand. let attr_list = pango::AttrList::new(); + add_pango_attributes(&attr_list, &pango_context, props); - let mut font_desc = pango_context.font_description().unwrap(); + layout.set_attributes(Some(&attr_list)); + layout.set_text(text); + layout.set_auto_dir(false); + + layout +} + +/// Adds Pango attributes, suitable for a span of text, to an `AttrList`. +fn add_pango_attributes( + attr_list: &pango::AttrList, + context: &pango::Context, + props: &FontProperties, +) { + let mut font_desc = context.font_description().unwrap(); font_desc.set_family(props.font_family.as_str()); font_desc.set_style(pango::Style::from(props.font_style)); @@ -1231,12 +1245,6 @@ fn create_pango_layout(draw_ctx: &DrawingCtx, props: &FontProperties, text: &str // smcp - small capitals - https://docs.microsoft.com/en-ca/typography/opentype/spec/features_pt#smcp attr_list.insert(pango::Attribute::new_font_features("'smcp' 1")); } - - layout.set_attributes(Some(&attr_list)); - layout.set_text(text); - layout.set_auto_dir(false); - - layout } #[cfg(test)] -- GitLab From e52ada9704dad98a89905be9279ce196e86003bd Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 25 Oct 2021 18:32:46 -0500 Subject: [PATCH 09/17] Remove comment that will become irrelevant soon We'll implement line-height by hand with the SVG2 text layout algorithm. Part-of: --- src/text.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/text.rs b/src/text.rs index eb276a77e..66e478a94 100644 --- a/src/text.rs +++ b/src/text.rs @@ -1185,18 +1185,6 @@ fn create_pango_layout(draw_ctx: &DrawingCtx, props: &FontProperties, text: &str let layout = pango::Layout::new(&pango_context); - // FIXME: For now we ignore the `line-height` property, even though we parse it. - // We would need to do something like this: - // - // layout.set_line_spacing(0.0); // "actually use the spacing I'll give you" - // layout.set_spacing(to_pango_units(???)); - // - // However, Layout::set_spacing() takes an inter-line spacing (from the baseline of - // one line to the top of the next line), not the line height (from baseline to - // baseline). - // - // Maybe we need to implement layout of individual lines by hand. - let attr_list = pango::AttrList::new(); add_pango_attributes(&attr_list, &pango_context, props); -- GitLab From a77f1754854f53f14b1495f1878ec574fed61025 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 25 Oct 2021 18:43:26 -0500 Subject: [PATCH 10/17] Do not modify the font description from the pango::Context Create a new pango::FontDescription every time. Per Pango's docs, the context's font description should not be modified. Part-of: --- src/text.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/text.rs b/src/text.rs index 66e478a94..d76d4b20f 100644 --- a/src/text.rs +++ b/src/text.rs @@ -1186,7 +1186,7 @@ fn create_pango_layout(draw_ctx: &DrawingCtx, props: &FontProperties, text: &str let layout = pango::Layout::new(&pango_context); let attr_list = pango::AttrList::new(); - add_pango_attributes(&attr_list, &pango_context, props); + add_pango_attributes(&attr_list, props); layout.set_attributes(Some(&attr_list)); layout.set_text(text); @@ -1196,12 +1196,8 @@ fn create_pango_layout(draw_ctx: &DrawingCtx, props: &FontProperties, text: &str } /// Adds Pango attributes, suitable for a span of text, to an `AttrList`. -fn add_pango_attributes( - attr_list: &pango::AttrList, - context: &pango::Context, - props: &FontProperties, -) { - let mut font_desc = context.font_description().unwrap(); +fn add_pango_attributes(attr_list: &pango::AttrList, props: &FontProperties) { + let mut font_desc = pango::FontDescription::new(); font_desc.set_family(props.font_family.as_str()); font_desc.set_style(pango::Style::from(props.font_style)); -- GitLab From 4c21fcd516628cbe8edd22dc02ce275826c34f61 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 25 Oct 2021 19:00:12 -0500 Subject: [PATCH 11/17] Create each pango::Attribute with a range within the layout's textg For now the range spans the whole text, since the code doesn't build a single pango::Layout per element yet. This commit is in preparation for that. Part-of: --- src/text.rs | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/src/text.rs b/src/text.rs index d76d4b20f..02eb97ad1 100644 --- a/src/text.rs +++ b/src/text.rs @@ -1186,7 +1186,7 @@ fn create_pango_layout(draw_ctx: &DrawingCtx, props: &FontProperties, text: &str let layout = pango::Layout::new(&pango_context); let attr_list = pango::AttrList::new(); - add_pango_attributes(&attr_list, props); + add_pango_attributes(&attr_list, props, 0, text.len()); layout.set_attributes(Some(&attr_list)); layout.set_text(text); @@ -1196,7 +1196,17 @@ fn create_pango_layout(draw_ctx: &DrawingCtx, props: &FontProperties, text: &str } /// Adds Pango attributes, suitable for a span of text, to an `AttrList`. -fn add_pango_attributes(attr_list: &pango::AttrList, props: &FontProperties) { +fn add_pango_attributes( + attr_list: &pango::AttrList, + props: &FontProperties, + start_index: usize, + end_index: usize, +) { + let start_index: u32 = cast::u32(start_index).expect("Pango attribute index must fit in u32"); + let end_index: u32 = cast::u32(end_index).expect("Pango attribute index must fit in u32"); + + let mut attributes = Vec::new(); + let mut font_desc = pango::FontDescription::new(); font_desc.set_family(props.font_family.as_str()); font_desc.set_style(pango::Style::from(props.font_style)); @@ -1209,25 +1219,39 @@ fn add_pango_attributes(attr_list: &pango::AttrList, props: &FontProperties) { font_desc.set_stretch(pango::Stretch::from(props.font_stretch)); font_desc.set_size(to_pango_units(props.font_size)); - attr_list.insert(pango::Attribute::new_font_desc(&font_desc)); - attr_list.insert(pango::Attribute::new_letter_spacing(to_pango_units( + attributes.push(pango::Attribute::new_font_desc(&font_desc)); + + attributes.push(pango::Attribute::new_letter_spacing(to_pango_units( props.letter_spacing, ))); if props.text_decoration.underline { - attr_list.insert(pango::Attribute::new_underline(pango::Underline::Single)); + attributes.push(pango::Attribute::new_underline(pango::Underline::Single)); } if props.text_decoration.strike { - attr_list.insert(pango::Attribute::new_strikethrough(true)); + attributes.push(pango::Attribute::new_strikethrough(true)); } // FIXME: Using the "smcp" OpenType feature only works for fonts that support it. We // should query if the font supports small caps, and synthesize them if it doesn't. if props.font_variant == FontVariant::SmallCaps { // smcp - small capitals - https://docs.microsoft.com/en-ca/typography/opentype/spec/features_pt#smcp - attr_list.insert(pango::Attribute::new_font_features("'smcp' 1")); + attributes.push(pango::Attribute::new_font_features("'smcp' 1")); + } + + // Set the range in each attribute + + for attr in &mut attributes { + attr.set_start_index(start_index); + attr.set_end_index(end_index); + } + + // Add the attributes to the attr_list + + for attr in attributes { + attr_list.insert(attr); } } -- GitLab From 6f9b6f168af49223cdf967e23c76572ba1540c08 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 25 Oct 2021 19:53:50 -0500 Subject: [PATCH 12/17] Fix comment Part-of: --- src/text.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/text.rs b/src/text.rs index 02eb97ad1..a9bd9d706 100644 --- a/src/text.rs +++ b/src/text.rs @@ -525,8 +525,14 @@ fn children_to_chunks( } Element::Link(ref link) => { - // TSpan::default tes all offsets to 0, + // TSpan::default sets all offsets to 0, // which is what we want in links. + // + // FIXME: This is the only place in the code where an element's method (TSpan::to_chunks) + // is called with a node that is not the element itself: here, `child` is a Link, not a TSpan. + // + // The code works because the `tspan` is dropped immediately after calling to_chunks and no + // references are retained for it. let tspan = TSpan::default(); let cascaded = CascadedValues::new(cascaded, &child); tspan.to_chunks( -- GitLab From f5184354022666cc576f2fb3e01b0394ad47ec4b Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 25 Oct 2021 19:58:06 -0500 Subject: [PATCH 13/17] text.rs: Use a mutable DrawingCtx only in Text::draw() Part-of: --- src/text.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/text.rs b/src/text.rs index a9bd9d706..84fa2d856 100644 --- a/src/text.rs +++ b/src/text.rs @@ -424,7 +424,7 @@ impl PositionedSpan { fn layout( &self, acquired_nodes: &mut AcquiredNodes<'_>, - draw_ctx: &mut DrawingCtx, + draw_ctx: &DrawingCtx, view_params: &ViewParams, ) -> LayoutSpan { let params = NormalizeParams::new(&self.values, view_params); @@ -484,7 +484,7 @@ fn children_to_chunks( node: &Node, acquired_nodes: &mut AcquiredNodes<'_>, cascaded: &CascadedValues<'_>, - draw_ctx: &mut DrawingCtx, + draw_ctx: &DrawingCtx, dx: f64, dy: f64, depth: usize, @@ -683,7 +683,7 @@ impl Text { node: &Node, acquired_nodes: &mut AcquiredNodes<'_>, cascaded: &CascadedValues<'_>, - draw_ctx: &mut DrawingCtx, + draw_ctx: &DrawingCtx, x: f64, y: f64, ) -> Vec { @@ -923,7 +923,7 @@ impl TSpan { node: &Node, acquired_nodes: &mut AcquiredNodes<'_>, cascaded: &CascadedValues<'_>, - draw_ctx: &mut DrawingCtx, + draw_ctx: &DrawingCtx, chunks: &mut Vec, dx: f64, dy: f64, -- GitLab From 19f07cd73556f138d2b53932cb28d0be800626dc Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 25 Oct 2021 20:22:56 -0500 Subject: [PATCH 14/17] Require Pango 1.46 for PangoOverline Part-of: --- COMPILING.md | 2 +- Cargo.toml | 8 ++++---- configure.ac | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/COMPILING.md b/COMPILING.md index fe2a9ce11..44ce72f55 100644 --- a/COMPILING.md +++ b/COMPILING.md @@ -43,7 +43,7 @@ minimum version is listed here; you may use a newer version instead. * GObject-Introspection 0.10.8 * Gtk-doc 1.13 * Libxml2 2.9.0 -* Pango 1.44.0 +* Pango 1.46.0 The following sections describe how to install these dependencies on several systems. diff --git a/Cargo.toml b/Cargo.toml index 4560ec682..685468b17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,11 +12,11 @@ cairo-svg = { version = "1.16", optional = true } gdk-pixbuf = { name = "gdk-pixbuf-2.0", version = "2.20" } gio = { name = "gio-2.0", version = "2.24" } glib = { name = "glib-2.0", version = "2.50" } -pangocairo = "1.44" +pangocairo = "1.46" [package.metadata.system-deps.'cfg(windows)'] fontconfig = { version = "1.7", optional = true } -pangoft2 = { version = "1.44", optional = true } +pangoft2 = { version = "1.46", optional = true } harfbuzz = { version = "2.0", optional = true } freetype2 = { version = "20.0.14", optional = true } cairo = { version = "1.16", optional = true } @@ -26,7 +26,7 @@ libxml2 = { name = "libxml-2.0", version = "2.9", optional = true } [package.metadata.system-deps.'cfg(not(windows))'] fontconfig = { version = "1.7" } -pangoft2 = { version = "1.44" } +pangoft2 = { version = "1.46" } cairo = "1.16" cairo-gobject = "1.16" cairo-png = "1.16" @@ -60,7 +60,7 @@ markup5ever = "0.10" nalgebra = "0.27.1" num-traits = "0.2" once_cell = "1.2.0" -pango = { version="0.14.0", features = ["v1_44"] } +pango = { version="0.14.0", features = ["v1_46"] } pangocairo = "0.14.0" rayon = "1" rctree = "0.3.3" diff --git a/configure.ac b/configure.ac index f7230c5c1..85e60cc05 100644 --- a/configure.ac +++ b/configure.ac @@ -66,7 +66,7 @@ GIO_REQUIRED=2.24.0 GLIB_REQUIRED=2.50.0 HARFBUZZ_REQUIRED=2.0.0 LIBXML_REQUIRED=2.9.0 -PANGO_REQUIRED=1.44.0 +PANGO_REQUIRED=1.46.0 dnl =========================================================================== -- GitLab From 80a72e607443b4b05d28a4387fc994aacb332218 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 25 Oct 2021 20:32:01 -0500 Subject: [PATCH 15/17] (#17): Render text-decoration:overline It was parsed, but not rendered. This requires Pango 1.46. Fixes https://gitlab.gnome.org/GNOME/librsvg/-/issues/17 Part-of: --- src/text.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/text.rs b/src/text.rs index 84fa2d856..fcff557de 100644 --- a/src/text.rs +++ b/src/text.rs @@ -1232,6 +1232,10 @@ fn add_pango_attributes( props.letter_spacing, ))); + if props.text_decoration.overline { + attributes.push(pango::Attribute::new_overline(pango::Overline::Single)); + } + if props.text_decoration.underline { attributes.push(pango::Attribute::new_underline(pango::Underline::Single)); } -- GitLab From 1e53e859ff15531bf2bd820d2f7504eb062047af Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 25 Oct 2021 20:49:17 -0500 Subject: [PATCH 16/17] Don't put the writing-mode in FontProperties writing-mode is valid only for , not for individual spans. Part-of: --- src/layout.rs | 10 ++-------- src/text.rs | 20 +++++++++++++++----- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/layout.rs b/src/layout.rs index 19ebd63b3..c259e9107 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -18,7 +18,7 @@ use crate::properties::{ ClipRule, ComputedValues, Direction, FillRule, Filter, FontFamily, FontStretch, FontStyle, FontVariant, FontWeight, MixBlendMode, Opacity, Overflow, PaintOrder, ShapeRendering, StrokeDasharray, StrokeLinecap, StrokeLinejoin, StrokeMiterlimit, TextDecoration, - TextRendering, UnicodeBidi, WritingMode, XmlLang, + TextRendering, UnicodeBidi, XmlLang, }; use crate::rect::Rect; use crate::surface_utils::shared_surface::SharedImageSurface; @@ -121,7 +121,6 @@ pub struct Text { /// Font-related properties extracted from `ComputedValues`. pub struct FontProperties { pub xml_lang: XmlLang, - pub writing_mode: WritingMode, pub unicode_bidi: UnicodeBidi, pub direction: Direction, pub font_family: FontFamily, @@ -268,14 +267,9 @@ impl FontProperties { /// /// The `writing-mode` property is passed separately, as it must come from the `` element, /// not the `` whose computed values are being passed. - pub fn new( - values: &ComputedValues, - writing_mode: WritingMode, - params: &NormalizeParams, - ) -> FontProperties { + pub fn new(values: &ComputedValues, params: &NormalizeParams) -> FontProperties { FontProperties { xml_lang: values.xml_lang(), - writing_mode, unicode_bidi: values.unicode_bidi(), direction: values.direction(), font_family: values.font_family(), diff --git a/src/text.rs b/src/text.rs index fcff557de..6ec88ce93 100644 --- a/src/text.rs +++ b/src/text.rs @@ -334,7 +334,7 @@ impl MeasuredSpan { let view_params = draw_ctx.get_view_params(); let params = NormalizeParams::new(&values, &view_params); - let properties = FontProperties::new(&values, text_writing_mode, ¶ms); + let properties = FontProperties::new(&values, ¶ms); let bidi_control = BidiControl::from_unicode_bidi_and_direction( properties.unicode_bidi, @@ -342,7 +342,12 @@ impl MeasuredSpan { ); let with_control_chars = wrap_with_direction_control_chars(&span.text, &bidi_control); - let layout = create_pango_layout(draw_ctx, &properties, &with_control_chars); + let layout = create_pango_layout( + draw_ctx, + text_writing_mode, + &properties, + &with_control_chars, + ); let (w, h) = layout.size(); let w = f64::from(w) / f64::from(pango::SCALE); @@ -1166,14 +1171,19 @@ fn wrap_with_direction_control_chars(s: &str, bidi_control: &BidiControl) -> Str res } -fn create_pango_layout(draw_ctx: &DrawingCtx, props: &FontProperties, text: &str) -> pango::Layout { +fn create_pango_layout( + draw_ctx: &DrawingCtx, + writing_mode: WritingMode, + props: &FontProperties, + text: &str, +) -> pango::Layout { let pango_context = draw_ctx.create_pango_context(); if let XmlLang(Some(ref lang)) = props.xml_lang { pango_context.set_language(&pango::Language::from_string(lang.as_str())); } - pango_context.set_base_gravity(pango::Gravity::from(props.writing_mode)); + pango_context.set_base_gravity(pango::Gravity::from(writing_mode)); match (props.unicode_bidi, props.direction) { (UnicodeBidi::BidiOverride, _) | (UnicodeBidi::Embed, _) => { @@ -1185,7 +1195,7 @@ fn create_pango_layout(draw_ctx: &DrawingCtx, props: &FontProperties, text: &str } (_, _) => { - pango_context.set_base_dir(pango::Direction::from(props.writing_mode)); + pango_context.set_base_dir(pango::Direction::from(writing_mode)); } } -- GitLab From 849bb7e903bc80e04418c5efe352f7e4f42bd830 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 25 Oct 2021 20:57:34 -0500 Subject: [PATCH 17/17] Pass the text writing mode around in a new LayoutContext struct We'll use this struct to hold the state of the SVG2 text layout algorithm. The writing-mode property of the element is used throughout the algorithm, so we keep it in a single place now. Part-of: --- src/text.rs | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/text.rs b/src/text.rs index 6ec88ce93..a865c7381 100644 --- a/src/text.rs +++ b/src/text.rs @@ -23,6 +23,12 @@ use crate::space::{xml_space_normalize, NormalizeDefault, XmlSpaceNormalize}; use crate::transform::Transform; use crate::xml::Attributes; +/// The state of a text layout operation. +struct LayoutContext { + /// `writing-mode` property from the `` element. + writing_mode: WritingMode, +} + /// An absolutely-positioned array of `Span`s /// /// SVG defines a "[text chunk]" to occur when a text-related element @@ -119,14 +125,14 @@ impl Chunk { impl MeasuredChunk { fn from_chunk( + layout_context: &LayoutContext, chunk: &Chunk, - text_writing_mode: WritingMode, draw_ctx: &DrawingCtx, ) -> MeasuredChunk { let mut measured_spans: Vec = chunk .spans .iter() - .map(|span| MeasuredSpan::from_span(span, text_writing_mode, draw_ctx)) + .map(|span| MeasuredSpan::from_span(layout_context, span, draw_ctx)) .collect(); // The first span contains the (dx, dy) that will be applied to the whole chunk. @@ -156,9 +162,9 @@ impl MeasuredChunk { impl PositionedChunk { fn from_measured( + layout_context: &LayoutContext, measured: &MeasuredChunk, view_params: &ViewParams, - text_writing_mode: WritingMode, chunk_x: f64, chunk_y: f64, ) -> PositionedChunk { @@ -197,7 +203,7 @@ impl PositionedChunk { Direction::Rtl => (-advance.0, advance.1), }; - let rendered_position = if text_writing_mode.is_horizontal() { + let rendered_position = if layout_context.writing_mode.is_horizontal() { (start_pos.0 + dx, start_pos.1 - baseline_offset + dy) } else { (start_pos.0 + baseline_offset + dx, start_pos.1 + dy) @@ -231,7 +237,7 @@ impl PositionedChunk { let anchor_offset = text_anchor_offset( measured.values.text_anchor(), chunk_direction, - text_writing_mode, + layout_context.writing_mode, chunk_bounds.unwrap_or_default(), ); @@ -275,14 +281,14 @@ fn compute_baseline_offset( fn text_anchor_offset( anchor: TextAnchor, direction: Direction, - text_writing_mode: WritingMode, + writing_mode: WritingMode, chunk_bounds: Rect, ) -> (f64, f64) { let (w, h) = (chunk_bounds.width(), chunk_bounds.height()); let x0 = chunk_bounds.x0; - if text_writing_mode.is_horizontal() { + if writing_mode.is_horizontal() { match (anchor, direction) { (TextAnchor::Start, Direction::Ltr) => (-x0, 0.0), (TextAnchor::Start, Direction::Rtl) => (-x0 - w, 0.0), @@ -325,8 +331,8 @@ impl Span { impl MeasuredSpan { fn from_span( + layout_context: &LayoutContext, span: &Span, - text_writing_mode: WritingMode, draw_ctx: &DrawingCtx, ) -> MeasuredSpan { let values = span.values.clone(); @@ -344,7 +350,7 @@ impl MeasuredSpan { let with_control_chars = wrap_with_direction_control_chars(&span.text, &bidi_control); let layout = create_pango_layout( draw_ctx, - text_writing_mode, + layout_context.writing_mode, &properties, &with_control_chars, ); @@ -357,7 +363,7 @@ impl MeasuredSpan { assert!(w >= 0.0); assert!(h >= 0.0); - let advance = if text_writing_mode.is_horizontal() { + let advance = if layout_context.writing_mode.is_horizontal() { (w, 0.0) } else { (0.0, w) @@ -751,8 +757,6 @@ impl Draw for Text { let stacking_ctx = StackingContext::new(acquired_nodes, &elt, values.transform(), values); - let text_writing_mode = values.writing_mode(); - draw_ctx.with_discrete_layer( &stacking_ctx, acquired_nodes, @@ -760,6 +764,10 @@ impl Draw for Text { clipping, None, &mut |an, dc| { + let layout_context = LayoutContext { + writing_mode: values.writing_mode(), + }; + let mut x = self.x.to_user(¶ms); let mut y = self.y.to_user(¶ms); @@ -767,7 +775,7 @@ impl Draw for Text { let mut measured_chunks = Vec::new(); for chunk in &chunks { - measured_chunks.push(MeasuredChunk::from_chunk(chunk, text_writing_mode, dc)); + measured_chunks.push(MeasuredChunk::from_chunk(&layout_context, chunk, dc)); } let mut positioned_chunks = Vec::new(); @@ -776,9 +784,9 @@ impl Draw for Text { let chunk_y = chunk.y.unwrap_or(y); let positioned = PositionedChunk::from_measured( + &layout_context, chunk, &view_params, - text_writing_mode, chunk_x, chunk_y, ); -- GitLab