html2canvas icon indicating copy to clipboard operation
html2canvas copied to clipboard

line-height problem?

Open zdxxxxxx opened this issue 4 years ago • 26 comments

when I use line-height=height to center text vertically,I find it does not seem to be centered vertically in the picture generated by html2canvas.

Bug reports:

https://jsbin.com/cohugezaxo/1/edit?html,output

Specifications:

  • html2canvas version tested with: 1.0.0-rc.5
  • Browser & version: chrome 79.0.3945.88
  • Operating system: macos sierra 10.12.4

image

zdxxxxxx avatar Jan 09 '20 02:01 zdxxxxxx

Hi there, I've met your problem in my project these days, but in this case you've mentioned, I find it draws quite well...I'don't know how to repeat it again, looking forward for your help :-)

Specifications:

  • Browser & version: chrome 79.0.3945.88
  • Operating system: macos sierra 10.12.4

image

RickieWoo avatar Jan 09 '20 11:01 RickieWoo

Try to set 'font-family:serif'...

zdxxxxxx avatar Jan 10 '20 07:01 zdxxxxxx

Try to set 'font-family:serif'...

Thanks for your reply, I've solved my problem~Have a nice day:-)

RickieWoo avatar Jan 10 '20 09:01 RickieWoo

how do you solved it

xuanyazhang avatar Feb 24 '20 10:02 xuanyazhang

I met the same prolem, and i found that it's the problem of getRangeBounds; the rangeBounds's height of a TextNode may bigger than the container node's clientHeight;

image

and then when fillText with bounds.top + bounds.height and textBaseline: 'bottom' into canvas, the text will be lower than the original position

image

my solution is adjust all text nodes's top in clonedElement:

image

but i think this should be solved in the library itself.

yangxuanxing avatar May 07 '20 15:05 yangxuanxing

Try to set 'font-family:serif'...

Thanks for your reply, I've solved my problem~Have a nice day:-)

How do you solved this problem? Need your help.

BrightYang0801 avatar May 26 '20 03:05 BrightYang0801

Try to set 'font-family:serif'...

Thanks for your reply, I've solved my problem~Have a nice day:-)

How do you solved this problem? Need your help.

It seems html2canvas doesn't support some fonts, just try to disable them one by one, then you will find the unsupported one. in my case, it's FangZheng xxx.

RickieWoo avatar Jun 01 '20 07:06 RickieWoo

Try to set 'font-family:serif'...

Thanks for your reply, I've solved my problem~Have a nice day:-)

How do you solved this problem? Need your help.

It seems html2canvas doesn't support some fonts, just try to disable them one by one, then you will find the unsupported one. in my case, it's FangZheng xxx.

Yes,you are right! html2canvas doesn't support most fonts.

BrightYang0801 avatar Jun 09 '20 10:06 BrightYang0801

in html2canvas

first: different font with get different textnode top and height

line-height: 70px;
font-size: 50px;

in MacOs

in Chromium (chrome 84.0.4147.89 and new edge)

Ping Fang, sans-serif, serif will get 70px height and STKaiti is 50px height inside html2canvas so

this.ctx.fillText(text.text, text.bounds.left, text.bounds.top + text.bounds.height);

will fillText in different position image but in Safari and Firefox, html2canvas result is almost same with dom then I find that

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textBaseline

html2canvas set context.textBaseline to middle or bottom but it's useless to let different font-family vertical-align: middle in Chromium

I try all context.textBaseline values and find ideographic useful in Chromium and Safari

image

but ideographic will result in first image in FireFox

poi-mashiro avatar Jul 30 '20 10:07 poi-mashiro

in html2canvas

first: different font with get different textnode top and height

line-height: 70px;
font-size: 50px;

in MacOs

in Chromium (chrome 84.0.4147.89 and new edge)

Ping Fang, sans-serif, serif will get 70px height and STKaiti is 50px height inside html2canvas so

this.ctx.fillText(text.text, text.bounds.left, text.bounds.top + text.bounds.height);

will fillText in different position image but in Safari and Firefox, html2canvas result is almost same with dom then I find that

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textBaseline

html2canvas set context.textBaseline to middle or bottom but it's useless to let different font-family vertical-align: middle in Chromium

I try all context.textBaseline values and find ideographic useful in Chromium and Safari

image

but ideographic will result in first image in FireFox

Please explain me how can i fix this. Where should I write which code? Should I edit html2canvas.js or just edit in html2canvas(elemet).then(canvas => {--------});?

in my scene, I only need to support Chrome, so I change in line 6200 @1.0.0-rc.5

        CanvasRenderer.prototype.renderTextWithLetterSpacing = function (text, letterSpacing) {
            var _this = this;
            if (letterSpacing === 0) {
                this.ctx.fillText(text.text, text.bounds.left, text.bounds.top + text.bounds.height);
            }

to

        CanvasRenderer.prototype.renderTextWithLetterSpacing = function (text, letterSpacing) {
            var _this = this;
            if (letterSpacing === 0) {
                this.ctx.textBaseline = 'ideographic'
                this.ctx.fillText(text.text, text.bounds.left, text.bounds.top + text.bounds.height);
            }

I think you can try to design a new system to get same text.bounds.top and text.bounds.height for different font-family in all browsers such as Chrome, Edge, Safari, Firefox, and main mobile browsers or
set different canvas context.textBaseline in different browsers before use canvas context.fillText or is it possibility to use a div to

<div style="font-size: textnode-font-size; line-height: textnode-line-height; white-space: nowrap;">textnode</div>

then use div.clientHeight and top = recursion computation div.parent.offsetTop and div.parent.parent.offsetTop to fillText(text, left, top+div.clientHeight)

poi-mashiro avatar Sep 15 '20 04:09 poi-mashiro

in html2canvas

first: different font with get different textnode top and height

line-height: 70px;
font-size: 50px;

in MacOs

in Chromium (chrome 84.0.4147.89 and new edge)

Ping Fang, sans-serif, serif will get 70px height and STKaiti is 50px height inside html2canvas so

this.ctx.fillText(text.text, text.bounds.left, text.bounds.top + text.bounds.height);

will fillText in different position image but in Safari and Firefox, html2canvas result is almost same with dom then I find that

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textBaseline

html2canvas set context.textBaseline to middle or bottom but it's useless to let different font-family vertical-align: middle in Chromium

I try all context.textBaseline values and find ideographic useful in Chromium and Safari

image

but ideographic will result in first image in FireFox

Please explain me how can i fix this. Where should I write which code? Should I edit html2canvas.js or just edit in html2canvas(elemet).then(canvas => {--------});?

in my scene, I only need to support Chrome, so I change in line 6200 @1.0.0-rc.5

        CanvasRenderer.prototype.renderTextWithLetterSpacing = function (text, letterSpacing) {
            var _this = this;
            if (letterSpacing === 0) {
                this.ctx.fillText(text.text, text.bounds.left, text.bounds.top + text.bounds.height);
            }

to

        CanvasRenderer.prototype.renderTextWithLetterSpacing = function (text, letterSpacing) {
            var _this = this;
            if (letterSpacing === 0) {
                this.ctx.textBaseline = 'ideographic'
                this.ctx.fillText(text.text, text.bounds.left, text.bounds.top + text.bounds.height);
            }

I think you can try to design a new system to get same text.bounds.top and text.bounds.height for different font-family in all browsers such as Chrome, Edge, Safari, Firefox, and main mobile browsers or set different canvas context.textBaseline in different browsers before use canvas context.fillText or is it possibility to use a div to

<div style="font-size: textnode-font-size; line-height: textnode-line-height; white-space: nowrap;">textnode</div>

then use div.clientHeight and top = recursion computation div.parent.offsetTop and div.parent.parent.offsetTop to fillText(text, left, top+div.clientHeight)

Thanks. It work for me!

nurulalamador avatar Sep 15 '20 05:09 nurulalamador

in html2canvas

first: different font with get different textnode top and height

line-height: 70px;
font-size: 50px;

in MacOs

in Chromium (chrome 84.0.4147.89 and new edge)

Ping Fang, sans-serif, serif will get 70px height and STKaiti is 50px height inside html2canvas so

this.ctx.fillText(text.text, text.bounds.left, text.bounds.top + text.bounds.height);

will fillText in different position image but in Safari and Firefox, html2canvas result is almost same with dom then I find that

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textBaseline

html2canvas set context.textBaseline to middle or bottom but it's useless to let different font-family vertical-align: middle in Chromium

I try all context.textBaseline values and find ideographic useful in Chromium and Safari

image

but ideographic will result in first image in FireFox

Please explain me how can i fix this. Where should I write which code? Should I edit html2canvas.js or just edit in html2canvas(elemet).then(canvas => {--------});?

in my scene, I only need to support Chrome, so I change in line 6200 @1.0.0-rc.5

        CanvasRenderer.prototype.renderTextWithLetterSpacing = function (text, letterSpacing) {
            var _this = this;
            if (letterSpacing === 0) {
                this.ctx.fillText(text.text, text.bounds.left, text.bounds.top + text.bounds.height);
            }

to

        CanvasRenderer.prototype.renderTextWithLetterSpacing = function (text, letterSpacing) {
            var _this = this;
            if (letterSpacing === 0) {
                this.ctx.textBaseline = 'ideographic'
                this.ctx.fillText(text.text, text.bounds.left, text.bounds.top + text.bounds.height);
            }

I think you can try to design a new system to get same text.bounds.top and text.bounds.height for different font-family in all browsers such as Chrome, Edge, Safari, Firefox, and main mobile browsers or set different canvas context.textBaseline in different browsers before use canvas context.fillText or is it possibility to use a div to

<div style="font-size: textnode-font-size; line-height: textnode-line-height; white-space: nowrap;">textnode</div>

then use div.clientHeight and top = recursion computation div.parent.offsetTop and div.parent.parent.offsetTop to fillText(text, left, top+div.clientHeight)

It works for me!

7revor avatar Sep 17 '20 04:09 7revor

I improved it simply in line 6200

CanvasRenderer.prototype.renderTextWithLetterSpacing = function (text, letterSpacing) {
    var _this = this;
    if (letterSpacing === 0) {
        this.ctx.fillText(text.text, text.bounds.left, text.bounds.top + text.bounds.height);
    }
    // ...
};

to

CanvasRenderer.prototype.renderTextWithLetterSpacing = function (text, letterSpacing) {
    var _this = this;
    if (navigator.userAgent.indexOf('Firefox') === -1){
        // non-Firefox browser add this
        this.ctx.textBaseline = 'ideographic';
    }
    if (letterSpacing === 0) {
        this.ctx.fillText(text.text, text.bounds.left, text.bounds.top + text.bounds.height);
    }
    // ...
};

kooriookami avatar Nov 07 '20 06:11 kooriookami

in html2canvas

first: different font with get different textnode top and height

line-height: 70px;
font-size: 50px;

in MacOs

in Chromium (chrome 84.0.4147.89 and new edge)

Ping Fang, sans-serif, serif will get 70px height and STKaiti is 50px height inside html2canvas so

this.ctx.fillText(text.text, text.bounds.left, text.bounds.top + text.bounds.height);

will fillText in different position image but in Safari and Firefox, html2canvas result is almost same with dom then I find that

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textBaseline

html2canvas set context.textBaseline to middle or bottom but it's useless to let different font-family vertical-align: middle in Chromium

I try all context.textBaseline values and find ideographic useful in Chromium and Safari

image

but ideographic will result in first image in FireFox

Please explain me how can i fix this. Where should I write which code? Should I edit html2canvas.js or just edit in html2canvas(elemet).then(canvas => {--------});?

in my scene, I only need to support Chrome, so I change in line 6200 @1.0.0-rc.5

        CanvasRenderer.prototype.renderTextWithLetterSpacing = function (text, letterSpacing) {
            var _this = this;
            if (letterSpacing === 0) {
                this.ctx.fillText(text.text, text.bounds.left, text.bounds.top + text.bounds.height);
            }

to

        CanvasRenderer.prototype.renderTextWithLetterSpacing = function (text, letterSpacing) {
            var _this = this;
            if (letterSpacing === 0) {
                this.ctx.textBaseline = 'ideographic'
                this.ctx.fillText(text.text, text.bounds.left, text.bounds.top + text.bounds.height);
            }

I think you can try to design a new system to get same text.bounds.top and text.bounds.height for different font-family in all browsers such as Chrome, Edge, Safari, Firefox, and main mobile browsers or set different canvas context.textBaseline in different browsers before use canvas context.fillText or is it possibility to use a div to

<div style="font-size: textnode-font-size; line-height: textnode-line-height; white-space: nowrap;">textnode</div>

then use div.clientHeight and top = recursion computation div.parent.offsetTop and div.parent.parent.offsetTop to fillText(text, left, top+div.clientHeight)

it works ! thank you too much

hoicking avatar Nov 27 '20 06:11 hoicking

Maybe it is different for other peoples code, but in my case I was able to correct this with one line of code in the html2canvas file. Right before the mentioned letter spacing conditional add the following:

text.bounds.top = text.bounds.top - text.bounds.height

metawebmarketing avatar Sep 23 '21 23:09 metawebmarketing

Maybe it is different for other peoples code, but in my case I was able to correct this with one line of code in the html2canvas file. Right before the mentioned letter spacing conditional add the following:

text.bounds.top = text.bounds.top - text.bounds.height

for me it's half the text height (without modifying textBaseline):

text.bounds.top = text.bounds.top - text.bounds.height / 2

niaodan2b avatar Sep 24 '21 02:09 niaodan2b

Maybe it is different for other peoples code, but in my case I was able to correct this with one line of code in the html2canvas file. Right before the mentioned letter spacing conditional add the following: text.bounds.top = text.bounds.top - text.bounds.height

for me it's half the text height (without modifying textBaseline):

text.bounds.top = text.bounds.top - text.bounds.height / 2

@niaodan2b solution worked for me - not happy about having to monkey patch this though... any reason this can't be added to the source? @niklasvh - i'll work on a PR to include a passed option for text alignment

mmcgraw73 avatar Jul 29 '22 13:07 mmcgraw73

Another similar case which is fix my problem #2775

ahmadsufyan avatar Sep 19 '22 08:09 ahmadsufyan

I met the same prolem, and i found that it's the problem of getRangeBounds; the rangeBounds's height of a TextNode may bigger than the container node's clientHeight;

image

and then when fillText with bounds.top + bounds.height and textBaseline: 'bottom' into canvas, the text will be lower than the original position

image

my solution is adjust all text nodes's top in clonedElement:

image

but i think this should be solved in the library itself.

Text dropping down is still an issue for me. And this solution worked. Below is how I used it.

I added a css class to the elements/text that would need adjusting during the clone and used that to update the style accordingly.

html2canvas(
    document.querySelector('#container-element-target'), // html I am converting to canvas
    { // options
        onclone: (el) => {
            const elementsWithShiftedDownwardText = el.querySelectorAll('.shifted-text');
            elementsWithShiftedDownwardText.forEach(element => {
                // adjust styles or do whatever you want here
                element.style.transform = 'translateY(-50%)';
            });
        }
    }
).then(function(canvas) {
    // whatever you are doing with the canvas after creation
    document.body.appendChild(canvas);
});

Nonimpressed avatar Nov 07 '22 10:11 Nonimpressed

I am using Tailwind and if you are as well then this solution may be helpful.

/* global.css */
@tailwind base;
@layer base {
  img {
    @apply inline-block;
  }
}
@tailwind components;
@tailwind utilities;

https://github.com/niklasvh/html2canvas/issues/2775#issuecomment-1204988157

ShawnGregg avatar Nov 14 '22 22:11 ShawnGregg

Since html2canvas append div tag to body to get some metrics, we can do the following:

const downloadPdf = () => {
    const style = document.createElement('style');
    document.head.appendChild(style);
    style.sheet?.insertRule('body > div:last-child img { display: inline-block; }');

    html2canvas(element).then(canvas => {
      style.remove();
    });
  };

boompikachu avatar Nov 16 '22 05:11 boompikachu

Maybe it is different for other peoples code, but in my case I was able to correct this with one line of code in the html2canvas file. Right before the mentioned letter spacing conditional add the following: text.bounds.top = text.bounds.top - text.bounds.height

for me it's half the text height (without modifying textBaseline):

text.bounds.top = text.bounds.top - text.bounds.height / 2

This works for me! Thank you so much!

adrianovarlotta avatar Dec 15 '22 15:12 adrianovarlotta

Importing import { CanvasRenderer } from "html2canvas/dist/types/render/canvas/canvas-renderer"

throws module not found error

toto1384 avatar Dec 21 '22 13:12 toto1384

good job,thx

happychen666 avatar Feb 15 '23 06:02 happychen666

None of the above really worked for me, i dont know if DOM manipulation has changed within recent years but here is my two cents on how I achieved something workable.

      // Generate canvas element
      const html2canvasElement = await html2canvas(props.node, {...props.options, onclone(document, element) {

          // Select all text nodes
          const textNodeList = document
            .evaluate('descendant-or-self::text()', element, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);

          // Update each text node
          for(let i=0; i < textNodeList.snapshotLength; i++)
          {
            const textNode = textNodeList.snapshotItem(i);
            if (!textNode) {
              continue;
            }

            const parent = textNode.parentElement;
            if (!parent){
              continue;
            }

            // Get text node bounds
            const range = document.createRange();
            range.selectNode(textNode);
            const bounds = range.getBoundingClientRect();

            // Offset marginTop
            if (parent.style.marginTop)
            {
                throw new Error('Text with marginTop not supported');
            }
            parent.style.marginTop = `-${Math.ceil(bounds.height)}px`;
          }
      }});

tmitchel2 avatar Feb 08 '24 10:02 tmitchel2

I am using Tailwind and if you are as well then this solution may be helpful.

/* global.css */
@tailwind base;
@layer base {
  img {
    @apply inline-block;
  }
}
@tailwind components;
@tailwind utilities;

#2775 (comment)

OMG this save me a night

spartan-huyle avatar Feb 22 '24 17:02 spartan-huyle