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

Provide href attribute support in a elements #313

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

Conversation

linev
Copy link

@linev linev commented Oct 1, 2024

This PR provide possible solution for #312

To be able use jsPDF.link() method, one need to have valid bounding box for all child nodes of a element.

Seems to be, existing implementation of getBoundingBoxByChildren function is not correct.
First commit excludes dummy box (all 0) from consideration.
Also transformation attributes from child nodes are taken into account - for now only translations.

While text node is most probable candidate to be used inside a element - one have to provide reasonable bounding box implementation for it. In the PR second method is just workaround to avoid re-implementation of rendering again to just get bounding box. Provided solution works only for plain text without tspan elements inside.

Last commit implements GroupA class which extends normal Group rendering and creates link in the PDF document.
Here some internals of jsPDF context are used - maybe better solution can be provided.

@yGuy
Copy link
Member

yGuy commented Oct 1, 2024

Thanks!

At the very least we should build upon the fix for the getBoundingBoxByChildren function - however implementing it properly with the transformation should not be too difficult, either, so I guess this should be done, too, because even though translations are common, scales are probably at least as common and this seems to fail. Is this function never used in other contexts? I wonder how it could be that broken....

That said, it would be very helpful, if you could come up with a few test cases, ideally some that show the various problematic combinations with transforms etc. so that we have test cases, even if they fail.

@linev
Copy link
Author

linev commented Oct 1, 2024

If I am right - I cannot use Point class from jsPDF and therefore Matrix.applyToPoint() method is not available.

I add commit which directly applies x/y axis scales from the matrix.

As primary example one can use SVG from developer.mozilla.org. Also here is SVG with some transform attributes:

canvas

Copy link
Member

@HackbrettXXX HackbrettXXX left a comment

Choose a reason for hiding this comment

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

Thank you for the PR. Please add a test case and fix the bounding box transformations with the multVecMatrix function (see my other comment).

Comment on lines 15 to 30

export class GroupA extends Group {
protected async renderCore(context: Context): Promise<void> {
await super.renderCore(context)

const href = getAttribute(this.element, context.styleSheets, 'href')
if (href) {
const box = this.getBoundingBox(context)
const scale = context.pdf.internal.scaleFactor
const ph = context.pdf.internal.pageSize.getHeight()

context.pdf.link(scale*(box[0] * context.transform.sx + context.transform.tx),
ph - scale*(box[1] * context.transform.sy + context.transform.ty), scale*box[2], scale*box[3], { url: href })
}
}
}
Copy link
Member

Choose a reason for hiding this comment

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

I would suggest renaming the class to "Anchor" and moving it to its own file.

Comment on lines 13 to 23
if ((nodeBox[0] === 0) && (nodeBox[1] === 0) && (nodeBox[2] === 0) && (nodeBox[3] === 0))
return;
const transform = child.computeNodeTransform(context)
nodeBox[0] = nodeBox[0] * transform.sx + transform.tx
nodeBox[1] = nodeBox[1] * transform.sy + transform.ty
if (boundingBox.length === 0)
boundingBox = nodeBox;
else
Copy link
Member

Choose a reason for hiding this comment

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

The width and height of the bounding box also need to be transformed. You can use the multVecMatrix function from geometry.ts

Do not use dummy (all 0) bounding box when search
bonding box for all children.
Otherwise minimal values always will be 0.
Also take into account transformation attributes of child node -
it may differ from child to child.
Apply scale and translation, but not rotation yet
While `a` element typically used together with text,
one need to have estimation of bounding box for text.
To avoid reimplementation of complete text rendering logic
just remember text position and sizes as bounding box.

Implemented only for plain text, any usage of tspan will not work properly
It derives from Group class and uses same rendering.
Plus if `href` attribute specified - adds link with bounding box.
Requires usage of scale and page height values -
otherwise created link area does not match drawn text
This test also can be used to demonstrate missing
support of dominant-baseline attribute of the text
@linev
Copy link
Author

linev commented Oct 28, 2024

I rebase PR, introducing Anchor class instead of GroupA.

I did not try to use multVecMatrix while it is not trivial for the box computation.
One need to translate all 4 corners of the boundary box separately to get correct positions -
but then it is not that one really can use afterwards.

I add test/specs/anchor/ with several <a> elements.
Test also shows thatsvg2pdf.js does not support dominant-baseline attribute.
This can be fixed by separate PR.

Copy link
Member

@HackbrettXXX HackbrettXXX left a comment

Choose a reason for hiding this comment

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

Thanks for adding test cases. The dominant-baseline attribute is being implemented in #286.

I would love some test cases with links on elements other than text and maybe some rotated elements after rotation is considered in the bounding box.

Comment on lines +16 to +20
// TODO: take into account rotation matrix
nodeBox[0] = nodeBox[0] * transform.sx + transform.tx
nodeBox[1] = nodeBox[1] * transform.sy + transform.ty
nodeBox[2] = nodeBox[2] * transform.sx
nodeBox[3] = nodeBox[3] * transform.sy
Copy link
Member

Choose a reason for hiding this comment

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

In think we should still transform all 4 corners and then calculate the encompassing axis-aligned rectangle.

Copy link
Author

Choose a reason for hiding this comment

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

I will try

@linev
Copy link
Author

linev commented Oct 31, 2024

I would love some test cases with links on elements other than text and maybe some rotated elements after rotation is considered in the bounding box.

But one need to implement bounding box for such elements.

@linev
Copy link
Author

linev commented Oct 31, 2024

I tried to implement bounding box using multVecMatrix function.
And add test with rotated text inside anchor.

Not sure that method works properly - see produced pdf in the anchor-rotation test.
In the SVG I used text like:

<text dy="0.2em" transform="rotate(180) translate(40)" font-size="20">rotate(180)</text>

Here three different transformations are applied - rotation, translate X, translate Y.
Not sure that such transformation properly can be described by single matrix -
especially if one swap order like transform="translate(40) rotate(180)"

@HackbrettXXX
Copy link
Member

You're right. The bounding boxes of the rotated elements are not correct. The multVecMatrix function should theoretically be correct. At quick glance I couldn't figure out what the issue is. Maybe you can step-debug a bit? Or draw the boxes into the PDF for debugging?

@linev
Copy link
Author

linev commented Nov 5, 2024

Can we split this?

One PR is creation of URL link for <a> elements.

Second is proper calculation of bounding box in case of rotated <text> elements.

And maybe third is testing if bounding box for all other elements like <rect> or <path> gives reasonable results.

@HackbrettXXX
Copy link
Member

Yes, sure.

@linev
Copy link
Author

linev commented Nov 6, 2024

Yes, sure.

Then I removing last two commits and will create another PR - once this is finalized

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