TiddlyWiki / TiddlyWiki5

A self-contained JavaScript wiki for the browser, Node.js, AWS Lambda etc.
https://tiddlywiki.com/
Other
8.05k stars 1.19k forks source link

[IDEA] Enhance SVG support #6758

Open ittayd opened 2 years ago

ittayd commented 2 years ago

Is your feature request related to a problem? Please describe. SVGs seem to be second class to images in TW5. In particular, a tiddler with content type image/svg+xml is rendered as an image with a data blob as its src instead of the SVG as is. Using the SVG in other tiddlers through [img[]] has the same result.

In particular, this means SVGs cannot be styled

Describe the solution you'd like A tiddler with content type image/svg+xml will be rendered as an SVG DOM. An [svg[]] way of including SVGs in other tiddlers (does it make sense to make the [[]] format be open to extention with more types ?)

Describe alternatives you've considered There is a workaround which is to not use the correct content type and use transclusion, but that more awkward (in particular since transclusion doesn't allow to set dimensions or class, so a wrapper is required to achieve those)

Jermolene commented 2 years ago

Hi @ittayd the distinction between SVG documents and SVG fragments embedded into HTML documents is not imposed by TiddlyWiki, it is how SVG works. The SVG spec does not permit the contents of an embedded SVG document to be styled via external CSS.

TiddlyWiki prefers to use embedded SVG fragments precisely because they are styleable, and not sandboxed like images.

I had a quick search for resources explaining the difference and found this that looks helpful:

https://www.oreilly.com/library/view/modern-svg/9781492048527/ch01.html

ittayd commented 2 years ago

I'm not sure I follow. The Viz plugin (for graphviz diagrams) produces SVG snippets and I was able to style them. The mermaid plugin also styles the SVG snippets it produces.

Here is my stylesheet (a $:/tags/Stylesheet tiddler): ` .viz :is(polygon[fill="none"][stroke="#000000"], ellipse) { fill: <>; stroke: <>; }

.viz :is(path[stroke="#000000"], polyline[fill="none"][stroke="#000000"]) { stroke: <>; }

.viz :is(polygon[fill="#000000"][stroke="#000000"]) { fill: <>; stroke: <>; }

.viz > svg > g > polygon[fill="#ffffff"] { fill: <>; stroke: <>; } .viz text { fill: <>; } `

Jermolene commented 2 years ago

I'm not sure I follow. The Viz plugin (for graphviz diagrams) produces SVG snippets and I was able to style them. The mermaid plugin also styles the SVG snippets it produces.

From your CSS it looks like viz is producing SVG fragments, which are displayed within an HTML document, and thus can be styled by the CSS associated with the document.

I think you are asking for the same styling capability to be available for SVG images (ie complete SVG documents), but that is not possible with the current SVG specification.

pmario commented 2 years ago

I think you are asking for the same styling capability to be available for SVG images (ie complete SVG documents), but that is not possible with the current SVG specification.

I think he is asking for a "convenient workflow" that should be supported by TW out of the box.

As I do understand it.

Since it should be possible to easily modify images even if they are already included into TW, we do have a major conflict now. ... The workflow got complicated and involves a lot of manual work, to make the systems compatible.

I think that's the main problems he faces. ... I do understand that, because from time to time I do have a similar problem with SVGs and Inkscape SVG program. ... I don't have a solution, but some ideas.

  1. We could create a custom [svg[]] link, that also allows styling as done with the image link [img[]]
  2. We could create an "export tiddler with SVG header" button.
  3. We could create a custom importer that strips the file header and doesn't set the type field.

I think option 1 would be the simplest one.

No promises --- Just some thoughts.

Jermolene commented 2 years ago

Hi @pmario in general it is not possible to automatically convert a self-contained SVG image into an embeddable SVG fragment.

The problem is that SVG images use IDs to reference gradients and other resources within the files. The IDs are defined by the image program to be unique within the image, but there's no guarantee that they will be unique when multiple images are merged into the same document.

pmario commented 2 years ago

@ittayd .. Which types of diagrams do you use from draw.io?

pmario commented 2 years ago

The problem is that SVG images use IDs to reference gradients and other resources within the files. The IDs are defined by the image program to be unique within the image, but there's no guarantee that they will be unique when multiple images are merged into the same document.

That's right. I did have a look at some SVG exports. They do use IDs ... But I think we can ask them, how they are produced and if they could be UUIDs ... would be worth a try.

ittayd commented 2 years ago

I'm using fragments then. There are actually 3 use cases that I have: draw.io (use draw.io's 'embed svg' export, copy & paste), viz, mermaid where the last two create the SVG. Would be good to be able to style & include all.

And if self-contained images cannot be styled, then maybe there should be a way to treat fragements differently (different content type, an [embed-svg[]] inclusion)

TiddlyWiki prefers to use embedded SVG fragments precisely because they are styleable, and not sandboxed like images.

I didn't understand this, if I take an SVG fragment and paste it into a tiddler, then TW5 will render it as an image blob instead of leaving it as is. So it seems to me TW5 prefers to use SVGs as documents, and thus doesn't allow styling.

pmario commented 2 years ago

I didn't understand this, if I take an SVG fragment and paste it into a tiddler, then TW5 will render it as an image blob instead of leaving it as is. So it seems to me TW5 prefers to use SVGs as documents, and thus doesn't allow styling.

How does one of your SVG fragments look like?

ittayd commented 2 years ago
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="121px" viewBox="-0.5 -0.5 121 61">
  <defs/>
  <g>
    <rect x="0" y="0" width="120" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
    <g transform="translate(-0.5 -0.5)">
      <switch>
        <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
          <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 30px; margin-left: 1px;">
            <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
              <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">Tiddlywiki</div>
            </div>
          </div>
        </foreignObject>
        <text x="60" y="34" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">Tiddlywiki</text>
      </switch>
    </g>
  </g>
  <switch>
    <g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
    <a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank">
      <text text-anchor="middle" font-size="10px" x="50%" y="100%">Text is not SVG - cannot display</text>
    </a>
  </switch>
</svg>
pmario commented 2 years ago

That should be the one we fixed with https://tiddlywiki.com/prerelease already. It will be implemented as an SVG fragment and it can be rendered now.

ittayd commented 2 years ago

@pmario, can you point to the details of the change? I know of the fixes for the namespace (thanks!), but was not aware that using in a tiddler (with a content type and nice way to include) is also supported

pmario commented 2 years ago

That's the PR https://github.com/Jermolene/TiddlyWiki5/pull/6755 .. It was a low level fix for using inline SVGs. So it should fix it everywhere. ... If not, you should create a new issue.

TW does all the DOM handling itself using widgets. ... That allows us to use dynamic refresh if any tiddler is changed. If that tiddler isn't shown at the moment -- no refresh needed.

The specific problem was, that we did assign an SVG namespace to HTML elements, which the browser did ignore .... silently. ... It didn't show the element. That was the problem you found out. ... the hard way ;)

ittayd commented 2 years ago

@pmario please See the image (I used the empty wiki linked from the prerelease page). The SVG is rendered as an image blob when using the content type image/svg+xml. If I don't use the content type, it is rendered as an inline SVG, but then I can't use it elsewhere with [img[]]. I can use transclusion but then can't set the height, width or class. image

pmario commented 2 years ago

Which browser do you use? I'm using FF latest

NO content type

image

pmario commented 2 years ago

As I wrote in my "workflow" description. If it is type=image/svg+xml we can use [img[]] links.

If it is text/vnd.tiddlywiki it can be used as an "inline svg" ... BUT we can't use image links anymore. We have to use transclusions or macros to include them. As described in: https://github.com/Jermolene/TiddlyWiki5/issues/6730#issuecomment-1162887928 and the following posts

ittayd commented 2 years ago

@pmario indeed, if I don't specify the content type, then the tiddler is rendered with an inline SVG. But, I can't use it in other tiddlers with [img[]], only transclusion, which doesn't allow me to specify height / width or class.

The ask here is to get both: so the tiddler content is rendered as an inline SVG and that it can be included with image links.

pmario commented 2 years ago

OK. The way I would go now, from all the info I know about your workflow.

4 tiddlers. The zip contains the tiddlers I did test it

test-custom-svg-styling.zip

It depends on you, if you want to with tags or titles for styling. The mechanism should give maximum flexibility by minimum maintenance. If you put some thought into the style definitions and the possibilities custom styling using data-tags give us.

title: svg-styles
tag: $:/tags/Stylesheet

[data-class="test.svg"] svg {
  border: 2px solid pink;
  width: 200px;
  height: 300px;
}

[data-class="a-tag"] svg {
  border: 2px solid green;
  width: 160px;
 padding: 20px;
}
title: svg-macros
tags: $:/tags/Macro

\define xsvg(title)
\whitespace trim
<div data-class=<<__title__>> >
<$transclude tiddler=<<__title__>> />
</div>
\end

\define tsvg(title)
\whitespace trim
<div data-class={{$title$!!tags}} >
<$transclude tiddler=<<__title__>> />
</div>
\end
title: test-tiddler

The xsvg macro uses the tiddler title to define the `data-class` attribute

<<xsvg "test.svg">>

The tsvg macro uses the SVG tiddler tags to define the `data-class` attribute

<<tsvg "test.svg">>
ittayd commented 2 years ago

Thanks and that is what I'm doing, but it is not as comfortable as using a content type and [img[]] directive (which is ease of use as well)

ittayd commented 2 years ago

Note that things like height and width are not so easy since there are no such attributes for a div, so need to revert to using an element style (when it's a per instance and not some global styling), which is cumbersome

pmario commented 2 years ago

Note that things like height and width are not so easy since there are no such attributes for a div, so need to revert to using an element style (when it's a per instance and not some global styling), which is cumbersome

Every element can use width and height but in this case we have to be more specific, since SVG has width and height attributes set. So it won't inherit those settings from it's DIV parent. ...

This specificity can be changed, if the SVG would have a class attribute. But if we don't want to manually adjust the SVG it has to be done as in the example.

pmario commented 2 years ago

Thanks and that is what I'm doing, but it is not as comfortable as using a content type and [img[]] directive (which is ease of use as well)

The content type image/svg+xml is an official MIME-type that is part of the image main types browsers can handle ... Those image main types can all be used, using the html <img> element. Since different image files like JPG, PNG and SVG have fundamentally different content, browsers need a specification how to deal with that content. ...

Since we want to be able to use the [img[xxx]] wikitext link with different image formats we use the <img> element. If that IMG element is used every browser will encode the image into a img:data element. We CAN NOT change that.

If we want to use SVG as inline SVG we can either create some macros, as I did here, or we can create a new [svg[xx]] image link, that can only handle inline SVGs.

As Jeremy pointed out at: https://github.com/Jermolene/TiddlyWiki5/issues/6758#issuecomment-1175982429 it's not possible to directly use every SVG as an inline SVG, since there are some attributes like IDs that will cause problems.

TW uses handcrafted SVGs for all icons, because we can style them. We can control how those SVGs look like. ... BUT ... we can not control, how SVGs look, that are imported by users.

If they want to use it with [img[]] links we use the IMG html element and the browser deals with it. ... No problems

If users want to use inline SVGs so they can use CSS for styling, they have to use macros and they have to take care, that the SVG content doesn't cause problems. ...

period

ittayd commented 2 years ago

According to MDN, div doesn't support height/width attributes (not talking about css properties).

Can't TW invent a mime type (much like for tiddlers) for inline SVGs (or, "svgs that TW should not convert to img with blob")? Maybe then TW can know how to treat them when included through [img[]]

joshuafontany commented 2 years ago

I am interested in using .SVG files and inline SVG+XML content soon to build UIs with TW. I'll check into this. It wouldn't really be hard to write an [svg[]] widget that would allow you to wrap the transcluded svg in an element in order to style it.

pmario commented 2 years ago

I am interested in using .SVG files and inline SVG+XML content soon to build UIs with TW. I'll check into this. It wouldn't really be hard to write an [svg[]] widget that would allow you to wrap the transcluded svg in an element in order to style it.

That's true. But the devil is in the details. See: https://github.com/Jermolene/TiddlyWiki5/issues/6758#issuecomment-1175982429

joshuafontany commented 2 years ago

@pmario After some SVG research, it seems like TW already has the capacity to make IDs unique because we parse the raw HTML/SVG code and use widgets to render it. Appending the tiddler-title to each ID would be a safe enough fix. Even if the tiddler is transcluded mutliple times, the "(non)unique IDs" all point to the right data. Another idea would be to internally use the <<qualify>> functionality to generate a unique-to-the-widget-tree-position ID.

Ref: https://github.com/svg/svgo

pmario commented 2 years ago

Another idea would be to internally use the <<qualify>> functionality to generate a unique-to-the-widget-tree-position ID.

The <<qualify>> function can only create a uniqe name on the tiddler level, and not on the widget level

joshuafontany commented 2 years ago

@pmario which is why appending the "title of the SVG tiddler" to all the IDs inside the SVG is the way to start, and I honestly think that can be done at render time. That should be enough to make it work. For example, if you translcude [[ThisIcon.svg]] around all over the place, but all of the IDs inside read ThisIcon.svg/ID####, then anytime that specific ID is called as url(ID), the DOM will pick up the first one rendered with id="ThisIcon.svg/ID####" and find the "right" gradient, effect, etc. I will do some more research.

Jermolene commented 2 years ago

@pmario After some SVG research, it seems like TW already has the capacity to make IDs unique because we parse the raw HTML/SVG code and use widgets to render it. Appending the tiddler-title to each ID would be a safe enough fix. Even if the tiddler is transcluded mutliple times, the "(non)unique IDs" all point to the right data. Another idea would be to internally use the <<qualify>> functionality to generate a unique-to-the-widget-tree-position ID.

Hi @joshuafontany it is correct that we can construct SVG fragments that use wikitext features to ensure unique IDs, and it is a useful technique (I did some work on wikitext generation of SVGs here).

But the problem in the OP is the desire to be able to use already existing SVG images that expect to be viewed as an image as SVG fragments that expect to be embedded in HTML. The implication is that we'd need a process to automatically qualify the IDs referred to in the SVG. Way back in 2011/12 I did experiment with something along those lines as part of TW5, but found that a general purpose, universal solution would be too complicated to be able to explain to end users.

I think there's scope for tools to help with the conversion process, but I'd be more hopeful that they would take the form of interactive authoring aids, rather than attempting solve the problem in a general way.

ittayd commented 2 years ago

@Jermolene, you're right about the id references that are inside the svg already.

I encountered it with the mermaid plugin that used mermaid.js that doesn't qualify ids. I ended up solving this by modifying the widget to put the svg inside a shadow dom.

That created another problem which is that this dom doesn't have the styling tiddlers. It's on my todo list to have the widget pull them, but it'll be nice if this wrapping and pulling will be part of tw5, so everyone can use it in a consistent way.