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

Implement textLength support #160

Merged
merged 7 commits into from Nov 11, 2020

Conversation

Mrfence97
Copy link
Contributor

This PR adds support for the textLength attribute for <text> nodes, solving issue #69.

The length is set by inserting appropriate spaces of length charSpace between characters. To calculate charSpace, the length ('default length') of the text is calculated using measureTextWidth() as if textLength wasn't applied.

The implementation when the <text> node just contains plain text is straightforward. However, if <tspan>s are present, the implementation is more complicated to account for e.g. different font sizes, as the default length must be calculated for each <tspan> at a time and then summed. Note this means that TextChunks aren't immediately put() to the PDF when a new chunk starts, instead they are stored in an array so these width calculations can occur beforehand.

Note textLength implementations differ between browsers, this PR follows the Firefox implementation:

  • Firefox

    • textLength is applied to a <text> element and all child <tspans> are scaled to fit within the 'global' length.
    • setting dx or an absolute x on a <tspan> has no impact on the charSpace.
    • if xml:space="default" any space/tab/newline characters present at the very start of the text are treated as a single character when calculating charSpace, but not rendered as such.
  • Chrome (not implemented, only for reference)

    • textLength is applied to individual <tspan>s.
    • setting dx or an absolute x impacts charSpace as the 'default length' is increased by the dx amount.
    • if xml:space="default" any space/tab/newline characters present are trimmed and not displayed or factored into the charSpace calculation.

- `textLength` implementations differ between browsers, this uses the
Firefox convention where it is applied to a `<text>` element (and _not_
to individual `<tspan>`s)
- Compile time flag sets whether to use the Firefox convention of
inserting a charSpace after the final character, or the Chrome
convention of not doing so. Currently uses Chrome convention.
- Adds a new unit test also
- If `xml:space="default"` then treats any prefixed 'space-like'
characters as being a single character of length 1 when calculating
charSpace
- Also include the updated `tests.js` file
}
if (i === tSpanCount - 1) {
if (i === this.element.childNodes.length - 1) {
Copy link
Contributor Author

@Mrfence97 Mrfence97 Nov 9, 2020

Choose a reason for hiding this comment

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

I think this line was originally a bug as this.element.childElementCount (tSpanCount) doesn't always equal this.element.childNodes.length. Given this loop is over this.element.childNodes, it makes sense to use the latter when checking for the last element in the array!

I think this is what is causing the tests to fail, however.

Copy link
Member

Choose a reason for hiding this comment

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

I agree, this should be this.element.childNodes.length - 1. The tests still fail, though, if I change this back to tSpanCount. Could you investigate why? If the changes are only minor floating point differences, you may override the reference PDF files.

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 this PR! A couple of things:

  • The textLength attribute is also allowed on tSpan elements. Did you consider this? I think this is not too important for now, but if you have time you may have a look at this.
  • Could you add test cases with x and y attributes on tSpans?
  • Maybe we should add a comment to the spec.svg that Chrome doesn't render this file correctly. As far as I can see, Firefox does a better job.

@@ -123,8 +150,11 @@ export class TextNode extends GraphicsNode {
if (xmlSpace === 'default') {
if (i === 0) {
trimmedText = trimLeft(trimmedText)
if (originalText.match(/^[(\n\r)|(\t)| ]/)) {
Copy link
Member

Choose a reason for hiding this comment

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

I think this regex and the others should be /^[\n\r\t ]/ or even /^\s/

}
if (i === tSpanCount - 1) {
if (i === this.element.childNodes.length - 1) {
Copy link
Member

Choose a reason for hiding this comment

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

I agree, this should be this.element.childNodes.length - 1. The tests still fail, though, if I change this back to tSpanCount. Could you investigate why? If the changes are only minor floating point differences, you may override the reference PDF files.

@Mrfence97
Copy link
Contributor Author

  • The textLength attribute is also allowed on tSpan elements. Did you consider this? I think this is not too important for now, but if you have time you may have a look at this.

Although the SVG spec states that it's allowed on <tspan>s, implementations appear to differ. Firefox simply ignores any textLengths that appear in a <tspan> and only ever considers the textLength in the parent <text> element. In Chrome, however, if a <tspan> has a textLength and has at least one of x/y/dx/dy set, then the prior content is stretched to fit the original textLength and the subsequent text is fit to the new textLength.

For example, this is how the following SVG is rendered in both Firefox and Chrome:

<rect y="0" width="200" height="20" fill="transparent" stroke="black"/>
<text textLength="200" y="20" font-family="times">Hello, <tspan y="40" textLength="100" fill="red">wor</tspan><tspan fill="yellow">ld!</tspan></text>

image

The PR matches Firefox and it I think it would be significant additional work to also implement the Chrome behaviour.

I'll add a test case that includes this example and also ones that set x and y. I'll also comment that it doesn't render correctly in Chrome!

I'll also verify what's causing the checks to fail, there should be no changes to any elements that don't have a textLength set so I'm assuming it'll just be floating point errors.

@Mrfence97
Copy link
Contributor Author

After resetting back to using tSpanCount, the test errors were all due to small numerical errors in four tests:

  1. complete-diagram2
  2. display-none-and-visibility-inheritance
  3. title-element
  4. xml-space

Switching back to this.element.childNodes.length - 1 a total of 10 tests fail (including those ones above). I haven't included the updated reference PDFs in the commit in case you wanted to review the differences (which will likely be more than just floating point errors). The very latest commit just adds the updated reference PDFs so you could checkout ea316e0 to look at the errors yourself.

I've also changed the regex and included a test case that sets x/y in a <tspan>.

@Mrfence97
Copy link
Contributor Author

It appears that my machine generates different reference PDFs to the test server...

I've pushed the reference PDFs that make all tests pass, but it seems the server generates different PDFs. Could this be because I'm running Windows? I've deleted and reinstalled all node packages and still all my tests pass. Strange goings on indeed.

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.

Alright, looks good now. I updated the references on my machine, lets see if that helps.

@HackbrettXXX
Copy link
Member

Seems like it did... thanks for this PR :)

@HackbrettXXX HackbrettXXX merged commit e11c4d5 into yWorks:master Nov 11, 2020
@Mrfence97 Mrfence97 deleted the feature/text-length branch November 11, 2020 10:25
@Mrfence97
Copy link
Contributor Author

Great, thanks for merging! Is there a road-map for when the next release will be made?

@HackbrettXXX
Copy link
Member

I would like to coordinate this release with the next jsPDF release, which I will hopefully be able to release within the next 2-3 weeks or so.

@yGuy
Copy link
Member

yGuy commented Nov 11, 2020

@Mrfence97 - thank you very much for the great work and your contribution! 'would love to see more quality PR requests. Let us know if there is a way we can say thank you and provide you with more awesome tasks ;-)

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.

None yet

3 participants