Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Realign foreignObject to its rightful position #2488

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

xworld21
Copy link
Contributor

But also, help! This started as an easy fix, and ended up touching alignment code. I think it is correct, but I'd rather have some comments now, before spending more time on it. I really hope I am right, because it improves the TikZ output in a small but quite substantial way.

The aim is to align foreign objects properly inside SVGs.

  1. Flip svg:foreignObject around its center, instead of the center of an imaginary line of height 16.6px (the hardcoded global value of \baselineskip). Compare with equivalent code in Post::SVG::convertNode. I have concluded that \baselineskip was just a mistake.

  2. Add a CSS flex wrapper to svg:foreignObject, so that the foreignObject box and its content share the same center, instead of the content being anchored north east, which looks bad in most situations.

(1) and (2) make normal TikZ figures (with no halign) perfect, modulo font metric errors. For instance,
image
becomes
image
Small difference, but the equal sign is where it should be. I have played with other figures and they all look better this way.

  1. (commits 3 + 4) Now TikZ-cd is even worse than before. The root cause: Core::Alignment hardcodes the depth to 0. That should rather be the depth of the last line. With the depth available, the heuristic code can be removed altogether. I seemingly get perfect alignment in TikZ-cd.

Example: before
image
after
image
which is much closer to the PDF
image

@dginev dginev requested a review from brucemiller January 15, 2025 19:49
@dginev
Copy link
Collaborator

dginev commented Jan 15, 2025

Very exciting work! @brucemiller please help :>

@xworld21 xworld21 force-pushed the realign-foreignobject-2 branch from 47c0673 to bfb3aeb Compare January 25, 2025 20:04
@xworld21 xworld21 marked this pull request as ready for review January 26, 2025 15:15
@xworld21 xworld21 force-pushed the realign-foreignobject-2 branch from df24b87 to b6fcdc4 Compare February 1, 2025 19:21
Comment on lines 342 to 345
$node->setAttribute(style => "line-height:${h}px");
$node->setAttribute(width => $w->pxValue) unless $node->hasAttribute('width');
$node->setAttribute(height => $ht) unless $node->hasAttribute('height');
$node->setAttribute(transform => "matrix(1 0 0 -1 0 $h)");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This took a few tries. Shifting by height only mimics better the behaviour of the depth, but it's not enough on its own. The additional line-height ensures that boxes with same horizontal coordinate and font size get the same baseline (this is noticeable when an arrow has multiple labels in tikzcd).

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quite possibly (or even probably) recording @rowdepths is a good thing, but it would be useful to have in its own PR, without the flex & div wrappers (I'd like to explore alternatives for that).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correction: line-height is just a mirage. I got good results using text-box which is very new CSS, not yet supported by Firefox. It might resolve that 'maths too high' issue you were mentioning.

I couldn't find any other way of aligning the blocks by baseline, unfortunately. I suggest we use text-box as the reference starting point and once it looks convincing, work on a fallback for older browsers.

</xsl:otherwise>
</xsl:choose>
</xsl:element>
</xsl:element>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This obscures the previous branch of foreignObject (from non svg namespace), with the side effect of also wrapping those cases with a pair of divs. The new code should have been its own branch below, before the otherwise.

Copy link
Contributor Author

@xworld21 xworld21 Mar 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In hindsight, I actually don't understand the test not(namespace-uri(child::*) = $SVG_NAMESPACE). This is checking whether the first child is not in SVG namespace. I think that maybe you want to check whether any child is not SVG and if there are non-trivial text nodes.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, that xpath test tries to say "not any", but ends up saying "not first".

Probably should be closer to:

count(child::*[namespace-uri(self) = $SVG_NAMESPACE]) = 0

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And text nodes. Maybe latexml never produces text there, but in principle foreignObject can contain character data.

@xworld21 xworld21 force-pushed the realign-foreignobject-2 branch from b6fcdc4 to 318ba9f Compare March 23, 2025 17:30
@xworld21
Copy link
Contributor Author

This is how it looks in Chrome as of 318ba9f.
image

@xworld21
Copy link
Contributor Author

I should summarize what 318ba9f is doing:

  • I used CSS text-box to make the bottom of (the content of) the foreignObject be its baseline. This seems to be impossible in old CSS, which is really a shame.
  • I used flex to do two things: align the bottom of the content (so its baseline) to the bottom of foreignObject, and to center the content horizontally. The former can probably be achieved with a single wrapper and bottom:0. The latter is more tricky, because the content will typically overflow so text-align:center doesn't cut it; this may require flex for real.

I am inclined to say that this is the correct approach. The rules are sound and predictable, and the output looks good in the early tests.

PS: text-box can make titled frames much more similar to the LaTeX ones.

@dginev
Copy link
Collaborator

dginev commented Mar 25, 2025

@xworld21 I assume you are aware of the limited availability of CSS text-box today? I wonder if we're risking breaking too much on Firefox if we use it for v0.8.9.

@xworld21
Copy link
Contributor Author

@xworld21 I assume you are aware of the limited availability of CSS text-box today? I wonder if we're risking breaking too much on Firefox if we use it for v0.8.9.

Yes, that is a major problem. I have a reasonable mitigation in mind though. I think my general idea here is that text-box is correct, so we might as well start with text-box as our baseline (no pun intended). I will add some extra CSS gated behind @supports with a less precise fallback (something like translate: 0 $depth;; it just takes a bit of finessing to make that happen on the right object, right depth, etc). I just haven't got round to it yet.

I wouldn't say it breaks Firefox, it's more that it breaks it differently compared to now.

@dginev
Copy link
Collaborator

dginev commented Mar 25, 2025

I wouldn't say it breaks Firefox, it's more that it breaks it differently compared to now.

This is the kind of comment I like to make. I like your plan.

@brucemiller
Copy link
Owner

Most of your latest examples, above, look pretty good, but I'm not getting as good results from your branch on the collection of tikz tests; Maybe I've screwed something up in comparison?
But as this branch is drifting from master, it's getting harder to compare code. If #2534 is appropriate, maybe we should merge it and rebase here?

@brucemiller
Copy link
Owner

The big difficulty I'm having is figuring out where xy, respectively pgf, "think they are" when they drop a foreignObject; that coupled with Firefox & Chrome occasionally having randomly different placement. I keep encountering trivially perfected cases, but if patched at the wrong level or context, inevitably break several other cases!

Of course, both systems rely completely on TeX's measurements to place things, put boxes around them, etc. But then the browsers use different fonts, and multiline text comes out particularly mismatched (eg the consort-flowchart test). I found that cranking down CSS line-height and letter-spacing helps a lot. But maybe more sophisticated CSS would be better? text-box sounds good on paper; I'm a bit sceptical that flex is the way to go, though.

I was surprised to see how LaTeXML's too-crude sizing of sub/superscripts threw xy way off. Of course, if you fix that without realizing the root cause, everything else goes off.

@xworld21 xworld21 force-pushed the realign-foreignobject-2 branch from 8401556 to 017bbf7 Compare March 27, 2025 20:45
@xworld21
Copy link
Contributor Author

I have implemented a hasty fallback for browsers not implementing text-box. It's not enough! Comments on the fallback:

  1. The fallback is via line-height and it is essentially equivalent to my previous version of this PR. This definitely causes issues with multiline content. I have reset line-height in inline-block children to mitigate some of it.
  2. Using CSS variables is debatable, but it makes it easy to toggle different switches on and off, so please ignore that for now!
  3. Google Chrome < 133 works well with the fallback.
  4. Safari 18.2 understands text-box, but I think it has a bug: if the content spans multiple lines, text-box trims under the first line rather than the last line. That... doesn't seem to be what the spec says.
  5. Firefox: hopeless. The line-height fallback seems to have the desired effect on text content, but MathML only moves by like one pixel. To see what I mean, try setting line-height:0px and see how it affects text vs math. So this needs some Firefox-specific workaround.

Talking of the foundations behind this PR, I should restate the principle, for clarity. TeX creates a box with a certain height, width, baseline; CSS creates a block with similar measures. What I am trying hard to do is to make the centre point of the baseline to be the same in TeX and in CSS. This is easy with text-box and flex; it kind of works with line-height for Blink/WebKit. Without those adjustments, the two boxes are attached at the northeast corner instead (and actually LaTeXML fudges that with an extra \baselineskip, which I have removed).

@xworld21
Copy link
Contributor Author

PS: also, my additional translate: fallback triggers an >20 years old bug in WebKit which completely breaks foreign objects, so that will need to be addressed as well (no point trying to perfect this until we understand Firefox, though).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants