ha-floorplan icon indicating copy to clipboard operation
ha-floorplan copied to clipboard

[FEATURE] relative paths break when embedding the SVG

Open LukeLR opened this issue 9 months ago • 16 comments

Describe the bug When the SVG image is in-line embedded in the homeassistant dashboard, relative links inside the SVG image will break, as the browser will try to fetch the resource relatively to the dashboard URL, not the SVG image URL. This affects for example raster images, which are not embedded in the SVG file.

For example, the floor plans of our rooms and lamp icons are available as PNG images, so my SVG image includes a PNG (example.png) which is in the same folder as the SVG image. When loading the SVG image directly in the browser using its URL http://<ha-ip>:8123/local/floorplan/floorplan.svg, the browser fetches the PNG from http://<ha-ip>:8123/local/floorplan/example.png. However, inside the floorplan card, the URL is interpreted relatively to the dashboard URL: http://<ha-ip>:8123/dashboard-test/example.png which doesn't work.

Embedding all the images in the SVG and using object clones in Inkscape is a workaround, but due to the base64 encoding of the embedded images, the SVG filesize blows up and slows down dashboard display. Using absolute links to the PNG on the HomeAssistant server inside the SVG is not really feasible for inkscape editing. As another workaround, I tried to rewrite the relative paths inside my SVG using JavaScript, but I can't find a way to execute JavaScript once after loading the document.

It would be great if relative links would work as expected inside the SVG image.

To Reproduce Steps to reproduce the behavior:

  1. Create an SVG in Inkscape
  2. Drag an external image into the Inkscape window
  3. Choose "Link"
  4. Upload that SVG to your HomeAssistant www directory
  5. Load the SVG directly in the browser
  6. Use the SVG in a floorplan card

Expected behavior The external image linked inside the SVG will display in the floorplan card. However, it will only be displayed when loading the SVG directly in the browser, not from inside the floorplan card.

Desktop (please complete the following information):

  • OS: Arch Linux
  • Browser: Firefox
  • Version: 137

Smartphone (please complete the following information):

  • Device: Fairphone 5
  • OS: /e/-OS
  • Browser: HomeAssistant app from f-droid
  • Version: -

Share configuration Please share:

  • YAML:
type: custom:floorplan-card
full_height: true
config:
  image: /local/floorplan/IMG_20250402_194639.svg
  stylesheet: /local/floorplan/IMG_20250402_194639.css
  rules: []
  • CSS: none
  • SVG-file (IMPORTANT!) (Use gofile.io, or something simular)

Image Image

If you create a issue without a SVG example, including the required YAML and CSS to reproduce the issue, it's likely what your issue will be closed, or marked as unresolvable, as it's lacking the proper amount of details. Thank you for understanding the situation.

Additional context Add any other context about the problem here.

LukeLR avatar Apr 06 '25 13:04 LukeLR

Hi,

Thank you for the bug-report. Hopefully I'll be able to look at it, in one of the upcoming days. Right now, I'm in the middle of implementing quite a few tests for ha-floorplan.

exetico avatar Apr 07 '25 18:04 exetico

Okay, no worries, take your time! Thanks a lot :)

LukeLR avatar Apr 08 '25 07:04 LukeLR

@LukeLR I'd kindly ask for a minimal example with both a SVG and a png file in it. Kindly share it in the issue description, and and notice me hereafter. Please do zip the two files, or share them at https://gofile.io/ or similar, as mentioned in the issue-template.

Update: Oh, you've already added the SVG, but ignored the comment about sharing it at gofile. I'll see if this is enough :-)

exetico avatar Apr 08 '25 15:04 exetico

Okay @LukeLR, I get your problem, but I must say, it's very user specific. And to me, it even looks like you have a option to fix it manually, by simply providing the full URL in the SVG.

Here I've tested with: xlink:href="http://google.com/IMG_20250402_194639.png" Image

Image

As ha-floorplan doesn't know where you store your assets, there's really only a single option: Add a option to define your path, for images, set with xlink:href.

But, how many images do you actually need to add, where you can't just use the image_set action, or define the full path to the image?

What do you expect ha-floorplan to do, if you have a image reference like this: xlink:href="IMG_20250402_194639.png"?

I could introduce a way to add a xlink_href_path option for the ha-floorplan config, and simply split any text before the file-name (removing anything before /, it present), and add the value of xlink_href_path` in front of it. But if this is a use-case for a few images, I think using one of the suggested options, would be more than fine, instead of introducing another feature, for a very specific need 😄?


Note to self: The best way of doing this, would be to add the logics in loadFloorplanSvg, and simply looking for images, and replace the paths, but, this doesn't handle a case, where SVG's added with image_set, also consumes external content.

    /* Normalize all href's within the SVG */
    const svgImageElements = this._querySelectorAll(
      svg,
      'image',
      true
    ) as SVGGraphicsElement[];

    const xLinkHrefProp = 'xlink:href';
    for (const svgImageElement of svgImageElements) {
      const xLinkHrefVal = svgImageElement.getAttribute(xLinkHrefProp);
      console.log(xLinkHrefVal);
      if (svgImageElement.nodeName === 'image' && xLinkHrefVal) {
        svgImageElement.setAttribute('xlink:href', 'bob_was_here.png');
        console.log(svgImageElement.getAttribute(xLinkHrefProp));
      }
    }

Image

exetico avatar Apr 08 '25 16:04 exetico

Thanks a lot for looking into this! I think it would be the easiest if relative paths inside the SVG would be interpreted relatively to the SVG location. This is the default when opening the SVG directly in a web browser, or when including the SVG image in an HTML page using <object>. The referenced images are stored right next to the SVG in the www folder of HomeAssistant. In that case, it wouldn't be necessary to tell floorplan a base location for the relative paths.

The SVG displays correctly in the Web browser or in Inkscape. In general, relative paths in the SVG work out of the box, but when the content of the SVG is included inline in the HTML of the HomeAssistant dashboard, which of course has a different URL than the SVG, relative paths are interpreted relatively to that URL instead of the SVG.

The images itself don't change, they are background images of the room plan or static images of the lights. In case of the lights, a CSS style adds an overlay to the image, to reflect the dimming / color state.

I hope this helps understanding the use case. Sorry for not sharing the file via GoFile, I thought using the GitHub attachment would also be ok.

LukeLR avatar Apr 09 '25 12:04 LukeLR

But, forcing a new way of defining the src of a given image, wouldn't that be a breaking change, at least for people which have previously hardcoded a full URL to a given image, or maybe with another trick?

It sounds like you'd like to have the path from image added, like /local/examples/home/, if the image key equals /local/floorplan/home/home.svg, right?

What if the given user likes to have things located at the /local/floorplan level, re-used for other SVG-files (multiple floorplans, or ha-floorplan used for other things, are a thing)? I'm not sure if, or else, how many, does use the image obj. in the SVG files.

I get the overall need, but I'm not really sure, what's the best thing to do? :-)

Let me know what you think. @OzGav Do you have some thoughts here?

Also, it's fine without GoFile 😄. Back in the days, SVG could just be a bit buggy in the discussions-area. But apparently it's not really the case anymore (?).

exetico avatar Apr 11 '25 13:04 exetico

I do often embed all my images and yes the file does get big but that is why the cache option was added.

An alternative solution would be to just place empty squares in the SVG and then use the image_set action to load them as needed. The advantage here is you have full control over the placement of those images which meets the option of an image used for a single floor plan or across multiple. I do this as well.

So not sure if a breaking change is warranted if there is a valid method to achieve the desired result?

OzGav avatar Apr 11 '25 13:04 OzGav

I've just noticed that Trzinka does mention his setup here: https://github.com/ExperienceLovelace/ha-floorplan/discussions/411 Here a href and xlink:href are added, pointing to /local/img/fp/background.png.

Introducing a feature without user control, would break his setup, and I'm sure other people are doing the same. As ha-floorplan offers a way to customize SVG-files, and not really trying to restrict how to store files in the local / www area, I'm sure it's done in plenty of ways, already 😄.

I'm personally more into a method, where the user defines a path for images, like image_resource_prefix:, and if defined, ha-floorplan will simply prepend the test-string. Maybe even reusing the host information, and generate a full URL like https//ha-destination/local/img/fp/background.png.

I do get the nice thing about having a given image rendered in InkScape, while working with the graphical elements, instead of a white box, or similar.

exetico avatar Apr 11 '25 14:04 exetico

Yes an optional key should allow this without affecting existing users

OzGav avatar Apr 11 '25 14:04 OzGav

Thanks for looking into this! This does not need to be a breaking change at all. Absolute paths (fully qualified URLs including protocol / hostname) and host-relative URLs starting with a / (e.g. /local/...) work fine, so they don't need to be changed. Only relative URLs, that means URLs that don't start with protocol / hostname or a /, need to be fixed. So everything that worked so far will remain working, only the relative paths that didn't work before will start working. Of course, it still wouldn't harm providing a configuration parameter to disable these.

LukeLR avatar Apr 11 '25 23:04 LukeLR

tl;dr: Type this into ChatGPT for an explanation and a few solutions: 'Give me an overview of absolute and relative path handling (loading external resources, NOT drawing paths) in iFrame and SVG.'

When you're working in a web browser, relative paths are always determined against the current URL. That would be the URL you have loaded in the browser, whether it is HA or the SVG. This is working as expected.

Even if something seems like an 'easy' fix, it isn't. Every added feature makes more opportunities for bugs, more potential support requests, and restricts the freedom to fix/implement other stuff in the future (more code = harder to change). It's worth evaluating a problem and figuring out whether the ongoing maintenance cost of a solution is worth it by asking: how critical is the problem? Is it blocking anything? Are there workarounds? Does this problem break user expectations or compatibility with other software? In this case I would say the issue does add a bit of friction, but doesn't completely block any use case. There are several easily achievable workarounds. And although it is inconvenient, this IS how I would expect it to work from a web developer point of view. This is how iFrames work too, so if it worked differently it would be inconsistent and unexpected.

Solutions

You have a variety of options:

  • Use absolute paths. This is personally what I'd recommend as it's the least faffing around. If you can access your HA instance over the internet this should already work with no additional configuration
  • Use an event handler (eg, by putting javascript in the onerror tag) to dynamically fix the URLs
  • Load the SVG in an object tag or embed tag

For more options check out the following links:

  • https://stackoverflow.com/questions/4476526/do-i-use-img-object-or-embed-for-svg-files/4482962#4482962
  • https://css-tricks.com/svg-fallbacks/
  • https://www.w3.org/Graphics/SVG/IG/resources/svgprimer.html#SVG_in_HTML

brandon-fryslie avatar Apr 16 '25 20:04 brandon-fryslie

The SVG works when loaded regularly in the browser. The paths break when HA floorplan embeds the image in a page at a different location, without providing the embedded resources at that location as well. In order to keep the relation working, one would either need to provide the resources at the new location as well, which is difficult, as we don't know which resources are referenced by the SVG. Or, one would need to walk over the SVG and prepend the directory path of the SVG to each relative reference (e.g. a reference to image.png becomes /local/floorplan/image.png when the SVG is included from /local/floorplan/floorplan.svg). This is probably the simplest fix, as the path of the SVG is known (specified in the configuration yml).

Of course, one could use absolute paths. But then the SVG breaks when the IP address / hostname of the HA instance changes. Some people even use different addresses when accessing HA from inside / outside the local network (VPN IPs). And it makes editing the image in inkscape more cumbersome. I don't know about a way to attach an event handler for doing the changes I described above manually. If this is definitely a wontfix for you, this would probably the way I'd do it if I knew how. Loading the SVG in an object or embed tag is not possible as well, as we can't influence the way HA floorplan loads the SVG.

LukeLR avatar Apr 16 '25 22:04 LukeLR

The SVG works when loaded regularly in the browser.

Of course, as that's the actual path there the other resources are loaded from.

I do understand what this could solve, in your workflow. I'll possible pick this up again, in the future. But it won't be the default logic, that's for sure :-)

exetico avatar Jun 02 '25 14:06 exetico

I don't think this is just about "my workflow", for me this is about conforming to standards, and not breaking things. But I can understand that you have a different way of viewing this, and therefore I'll stick with embedded images and having larger SVGs. Thanks nonetheless for the support and the discussion.

On June 2, 2025 4:32:16 PM GMT+02:00, Tobias Nordahl Kristensen @.***> wrote:

exetico left a comment (ExperienceLovelace/ha-floorplan#546)

The SVG works when loaded regularly in the browser.

Of course, as that's the actual path there the other resources are loaded from.

I do understand what this could solve, in your workflow. I'll possible pick this up again, in the future. But it won't be the default logic, that's for sure :-)

-- Reply to this email directly or view it on GitHub: https://github.com/ExperienceLovelace/ha-floorplan/issues/546#issuecomment-2931013781 You are receiving this because you were mentioned.

Message ID: @.***>

LukeLR avatar Jun 02 '25 19:06 LukeLR

I'm going to chime in here because I feel for @LukeLR in trying to explain the issue but not being grasped. The problem is, Inkscape and Web browsers are playing tug-a-war with each other in regards to links, it's actually mind boggling to me how the SVG's that can be embedded into html after being edited in visual software like Inkscape hasn't got it's own base path reference that can be set.

Prior to version 1.1 in Inkscape, I used href for the full path to the image for the browser eg . /local/floorplan/images/floorplan.png and xlink:href as the reference to the file for Inkscape to use. While it was a pain to use two separate attributes, it worked at least. Now post Inkscape version 1.1 and their mighty wisdom in always butchering the href to always match the xlink:href whenever you open the document. This is no longer an option.

Working with SVG and seeing the image in Inkscape is important! Using absolute URL's is not an option because external access and local access is different so using http://{some url here} will be different. Using /local/{some path here} is not an option because inkscape can't find the image. Doing relative of the SVG will show in Inkscape but not the browser.

For a card that specifically works with SVG as it's main purpose, I find it should try to resolve this issue. The argument that it would break other people's working setups doesn't ring true. This is only a problem with relative URLs, meaning no leading '/' and no relative URL I could imagine would be trying to access the home-assistants server assets at the current URL. I was also on the same process thought with LukeLR about using JS for example the following.

  startup_action:
    - service: floorplan.execute
      service_data: |
        >
         console.dir(this)

I checked all through this object and found no reference to the actual SVG. Furthermore, elements etc aren't even populated till after the startup action has been executed. In your documentation https://experiencelovelace.github.io/ha-floorplan/docs/usage/#start-up-actions it states "This feature is particularly useful for making initial changes to your SVG file" yet I can't even find reference. I could nail down to it through the DOM but that's bad programming because it can change at anytime with any update.

EDIT: I just looked at recent updates and saw that the root SVG was added as a feature. Will look into it but still doesn't solve other issues like redundant 404 calls etc.

FWIW, embedding also introduces other problems and a simple new render of a light would involve opening the SVG then re-saving (with a minute or two to actually save with the size of the file being the main factor), clearing cache etc. where as a reference and just changing the file makes everyone happy and uses less resources and saves instantly.

A simple root config option of something like image_base_path where we can assign the actual base path for images that use relative path would solve all these issues, if it's omitted then nothing needs to be done to configurations that already have a running and working setup. It should also only apply to href without a leading '/' or 'http'. Sounded like you guys were over thinking and over complicating it. a simple check for images being added to the DOM and the config entry is all that should be needed then prepend the image_base_path to the href if the condition is met and that's it, shouldn't need any rewrite or restructuring of code (granted I haven't analysed your code, but I couldn't imagine your current structure doesn't have an easy adoption for this).

I also find band aid fixes like hooking into errors and not rectifying at the top level as bad practice, making network calls to known URLs that don't exists (404) is also bad optimisation. If the check can be done and changed before the SVG is inserted into the DOM then network calls to missing URL's will never be attempted.

Right now I've reverted to using Inkscape 1.0 till hopefully there comes some unifying way to have everything work nicely in which I can finally update without introducing the headaches like last time I updated. As the images are added for each light after rendering etc. It's just not feasible to do floorplan.image_set and retype out each image over again when all the references are already there (I currently have over 30 images for the SVG).

Sian-Lee-SA avatar Sep 19 '25 20:09 Sian-Lee-SA

So.. After applying the latest update and actually having reference and access to the actual SVG, I solved most issues by

  startup_action:
    - service: floorplan.execute
      service_data: |
        >
         const base_path = '/local/floorplan/images';
         svg.querySelectorAll('image').forEach(e => {
            let href = e.getAttribute('href');
            if( !href || ["/", "http://", "#"].some(substr => href.startsWith(substr)) )
                return;
            e.setAttribute('href', `${base_path}/${href}`);
         })

But I still cringe on the network calls to the missing paths to the images on initial load.

EDIT: Turns out the relative URL that doesn't exist to the actual image isn't even 404, it's 200 still loading the same html content of the current dashboard. So 30+ network calls to the same dashboard html code. Ideally we would like this card to process this before adding to the DOM to avoid those calls.

Sian-Lee-SA avatar Sep 19 '25 21:09 Sian-Lee-SA