[librsvg: 2/4] Implement `:link` pseudo-class




commit 8c0791ef49bf0d76139dbd98bfa531fb76335a3e
Author: Michael Howell <michael notriddle com>
Date:   Fri Oct 8 17:22:45 2021 -0700

    Implement `:link` pseudo-class
    
    This also adds parsing for the `:visited` pseudo class,
    but since librsvg maintains no history, it's not much of an implementation.
    
    This change is included to make the test case in #738 pass,
    which doesn't just try to match an `a` selector, but a whole
    `a:link` selector.
    
    Part-of: <https://gitlab.gnome.org/GNOME/librsvg/-/merge_requests/601>

 src/css.rs                                         |  48 +++++++++++++++++----
 src/structure.rs                                   |   1 -
 tests/fixtures/reftests/a-pseudo-class-ref.png     | Bin 0 -> 3152 bytes
 tests/fixtures/reftests/a-pseudo-class.svg         |  30 +++++++++++++
 .../a-inside-text-content-pseudo-class-738-ref.png | Bin 0 -> 2493 bytes
 .../a-inside-text-content-pseudo-class-738.svg     |  26 +++++++++++
 6 files changed, 96 insertions(+), 9 deletions(-)
---
diff --git a/src/css.rs b/src/css.rs
index 3b19e795..1508060b 100644
--- a/src/css.rs
+++ b/src/css.rs
@@ -202,6 +202,19 @@ impl<'i> selectors::Parser<'i> for RuleParser {
         // Or are CSS namespaces completely different, declared elsewhere?
         None
     }
+    fn parse_non_ts_pseudo_class(
+        &self,
+        location: SourceLocation,
+        name: CowRcStr<'i>,
+    ) -> Result<NonTSPseudoClass, cssparser::ParseError<'i, Self::Error>> {
+        match &*name {
+            "link" => Ok(NonTSPseudoClass::Link),
+            "visited" => Ok(NonTSPseudoClass::Visited),
+            _ => Err(location.new_custom_error(
+                selectors::parser::SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name),
+            )),
+        }
+    }
 }
 
 // `cssparser::RuleListParser` is a struct which requires that we
@@ -300,14 +313,20 @@ impl<'i> AtRuleParser<'i> for RuleParser {
 /// Dummy type required by the SelectorImpl trait.
 #[allow(clippy::upper_case_acronyms)]
 #[derive(Clone, Debug, Eq, PartialEq)]
-pub struct NonTSPseudoClass;
+pub enum NonTSPseudoClass {
+    Link,
+    Visited,
+}
 
 impl ToCss for NonTSPseudoClass {
-    fn to_css<W>(&self, _dest: &mut W) -> fmt::Result
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
     where
         W: fmt::Write,
     {
-        Ok(())
+        match self {
+            NonTSPseudoClass::Link => write!(dest, "link"),
+            NonTSPseudoClass::Visited => write!(dest, "visited"),
+        }
     }
 }
 
@@ -491,15 +510,17 @@ impl selectors::Element for RsvgElement {
 
     fn match_non_ts_pseudo_class<F>(
         &self,
-        _pc: &<Self::Impl as SelectorImpl>::NonTSPseudoClass,
+        pc: &<Self::Impl as SelectorImpl>::NonTSPseudoClass,
         _context: &mut MatchingContext<'_, Self::Impl>,
         _flags_setter: &mut F,
     ) -> bool
     where
         F: FnMut(&Self, ElementSelectorFlags),
     {
-        // unsupported
-        false
+        match *pc {
+            NonTSPseudoClass::Link => self.is_link(),
+            NonTSPseudoClass::Visited => false,
+        }
     }
 
     fn match_pseudo_element(
@@ -513,8 +534,19 @@ impl selectors::Element for RsvgElement {
 
     /// Whether this element is a `link`.
     fn is_link(&self) -> bool {
-        // FIXME: is this correct for SVG <a>, not HTML <a>?
-        self.0.is_element() && is_element_of_type!(self.0, Link)
+        // Style as link only if href is specified at all.
+        //
+        // The SVG and CSS specifications do not seem to clearly
+        // say what happens when you have an `<svg:a>` tag with no
+        // `(xlink:|svg:)href` attribute. However, both Firefox and Chromium
+        // consider a bare `<svg:a>` element with no href to be NOT
+        // a link, so to avoid nasty surprises, we do the same.
+        // Empty href's, however, ARE considered links.
+        self.0.is_element()
+            && match &*self.0.borrow_element() {
+                crate::element::Element::Link(link) => link.link.is_some(),
+                _ => false,
+            }
     }
 
     /// Returns whether the element is an HTML <slot> element.
diff --git a/src/structure.rs b/src/structure.rs
index 8d47197d..28fde6d1 100644
--- a/src/structure.rs
+++ b/src/structure.rs
@@ -541,7 +541,6 @@ impl Draw for Link {
         draw_ctx: &mut DrawingCtx,
         clipping: bool,
     ) -> Result<BoundingBox, RenderingError> {
-
         // If this element is inside of <text>, do not draw it.
         // The <text> takes care of it.
         for an in node.ancestors() {
diff --git a/tests/fixtures/reftests/a-pseudo-class-ref.png b/tests/fixtures/reftests/a-pseudo-class-ref.png
new file mode 100644
index 00000000..f61ff300
Binary files /dev/null and b/tests/fixtures/reftests/a-pseudo-class-ref.png differ
diff --git a/tests/fixtures/reftests/a-pseudo-class.svg b/tests/fixtures/reftests/a-pseudo-class.svg
new file mode 100644
index 00000000..699160d8
--- /dev/null
+++ b/tests/fixtures/reftests/a-pseudo-class.svg
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:xlink="http://www.w3.org/1999/xlink";
+   version="1.1"
+   viewBox="0 0 500 600"
+   font-family="sans-serif"
+   font-size="18">
+  <defs>
+    <style>
+      a { fill: red; font-family: Helvetica; font-size:10; }
+      a:link { fill: black; }
+      
+      text { fill: inherit; font-family: Helvetica; font-size:10; }
+    </style>
+  </defs>
+
+  <text x="250" y="25" class="head" text-anchor="middle">SVG CSS Tests</text>
+  <g transform="translate(0,50)"><a xlink:href="#foo">
+    <text x="50">a:link</text>
+    <text x="250" class="test">xlink:href</text>
+  </a></g>
+  <g transform="translate(0,150)"><a>
+    <text x="50">a:link</text>
+    <text x="250" class="test">no href, not link</text>
+  </a></g>
+
+</svg>
diff --git a/tests/fixtures/reftests/bugs/a-inside-text-content-pseudo-class-738-ref.png 
b/tests/fixtures/reftests/bugs/a-inside-text-content-pseudo-class-738-ref.png
new file mode 100644
index 00000000..ea52230c
Binary files /dev/null and b/tests/fixtures/reftests/bugs/a-inside-text-content-pseudo-class-738-ref.png 
differ
diff --git a/tests/fixtures/reftests/bugs/a-inside-text-content-pseudo-class-738.svg 
b/tests/fixtures/reftests/bugs/a-inside-text-content-pseudo-class-738.svg
new file mode 100644
index 00000000..bc2203b2
--- /dev/null
+++ b/tests/fixtures/reftests/bugs/a-inside-text-content-pseudo-class-738.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:xlink="http://www.w3.org/1999/xlink";
+   version="1.1"
+   viewBox="0 0 500 600"
+   font-family="sans-serif"
+   font-size="18">
+  <defs>
+    <style>
+      a { fill: white; font-family: Helvetica; font-size:10; }
+      a:link { fill: black; }
+      
+      text { fill: black; font-family: Helvetica; font-size:10; }
+    </style>
+  </defs>
+
+  <text x="250" y="25" class="head" text-anchor="middle">SVG CSS Tests</text>
+  <g transform="translate(0,50)">
+    <text x="50">a:link</text>
+    <text x="250" class="test"><a xlink:href="#foo">xlink:href</a></text>
+  </g>
+
+</svg>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]