parallax / jsPDF

Client-side JavaScript PDF generation for everyone.
https://parall.ax/products/jspdf
MIT License
29.42k stars 4.68k forks source link

Provided ctx.textAlign and ctx.rotate , position for the text is hard to understand #2733

Open fzlee opened 4 years ago

fzlee commented 4 years ago

Version : v2.1.1 which is located in dist folder Code to reproduce this issue:

  const doc = jsPDF({
   orientation: 'l',
    unit: 'mm',
    format: [100, 100]
  })
  const ctx = doc.canvas.getContext('2d')

  ctx.translate(50, 50)
  ctx.rotate(this.toRAD(45))
  ctx.fillRect(0, 0, 2, 2)

  // ctx.textAlign = 'left' will work as expected
  ctx.textAlign = 'center'
  ctx.fillText('helloworld', 0, 0)

  doc.save("test.pdf")

In all, I want to draw 'hello world' right in the middle of the canvas and then rotate it 45°.

Setting ctx.textAlign = 'left'(which is the default value I think), position for the text is just as expected , the text starts right from middle of the canvas

While setting ctx.textAlign = 'center', things work differently. There is a small distance from the rect and the text.

In fact, you may refer to attached docs, position for the text is really hard to controll or to understand when you tried other degrees like 90 and 180.

no-align.pdf align-center-rotate45.pdf align-center-rotate90.pdf align-center-rotate180.pdf

You may try this example in your own laptop Archive.zip

HackbrettXXX commented 4 years ago

Yes, this seems like some matrices are not applied in the correct order.

github-actions[bot] commented 4 years ago

This issue is stale because it has been open 90 days with no activity. It will be closed soon. Please comment/reopen if this issue is still relevant.

seanmiddleditch commented 1 year ago

It appears the matrix application is fine. The problem to me seems that the text functions always apply the matrix to the lower-left corner of the bounding box of the text to find the new center point, then call into lower-level PDF functions to apply the actual rotation angle; the current matrix rotation is thus applied to the text origin and only extracted (as an angle) for that low-level rendering function.

This would work fine enough if the code adjusted the text origin based on alignment properties, e.g. used the mid-point of the bounding box's bottom edge (for center alignment; using the left or right point for left/right alignment, of course).

The following monkey patch fixes alignment support by adjusting the x parameter based on the current alignment. To be transparent, I've only test this on simple use cases, and it may break with some more intricate uses, but it does work with rotation and left/center/right justification. After applying this (and also the monkey patch from https://github.com/parallax/jsPDF/issues/3225 -- yes, you can apply both!) I get effectively identical output in both the HTML Canvas and jsPDF canvas for rendering rotated text with center or right alignment.

            const oldFillText = ctx.fillText
            ctx.fillText = (text, x, y, w) => {
                const oldAlign = ctx.textAlign
                const xs =
                    oldAlign == 'middle' || oldAlign == 'center' ? 0.5 :
                    oldAlign == 'right' || oldAlign == 'end' ? 1.0 :
                        0.0

                ctx.textAlign = 'left'
                const m = ctx.measureText(text)
                oldFillText.call(ctx, text, x - m.width * xs, y, w)
                ctx.textAlign = oldAlign
            }