Skip to content

Commit 15ca338

Browse files
committed
Fix text rendering for IE and Opera
1 parent 18d95d6 commit 15ca338

10 files changed

Lines changed: 225 additions & 125 deletions

File tree

.jshintrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@
1313
"globals": {
1414
"jQuery": true
1515
},
16-
"predef": ["NodeParser", "NodeContainer", "StackingContext", "TextContainer", "ImageLoader", "CanvasRenderer", "Renderer", "Support", "bind", "Promise",
16+
"predef": ["NodeParser", "NodeContainer", "StackingContext", "TextContainer", "ImageLoader", "CanvasRenderer", "Renderer", "Support", "bind", "Promise", "getBounds", "offsetBounds",
1717
"ImageContainer", "ProxyImageContainer", "DummyImageContainer", "Font", "FontMetrics", "GradientContainer", "LinearGradientContainer", "WebkitGradientContainer", "log", "smallImage", "parseBackgrounds"]
1818
}

build/html2canvas.js

Lines changed: 85 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ function renderDocument(document, options, windowWidth, windowHeight) {
3737
document.querySelector(selector).removeAttribute(html2canvasNodeAttribute);
3838
var clonedWindow = container.contentWindow;
3939
var node = clonedWindow.document.querySelector(selector);
40-
var support = new Support();
40+
var support = new Support(clonedWindow.document);
4141
var imageLoader = new ImageLoader(options, support);
42-
var bounds = NodeParser.prototype.getBounds(node);
42+
var bounds = getBounds(node);
4343
var width = options.type === "view" ? Math.min(bounds.width, windowWidth) : documentWidth();
4444
var height = options.type === "view" ? Math.min(bounds.height, windowHeight) : documentHeight();
4545
var renderer = new CanvasRenderer(width, height, imageLoader);
@@ -343,6 +343,7 @@ function NodeContainer(node, parent) {
343343
this.parent = parent;
344344
this.stack = null;
345345
this.bounds = null;
346+
this.offsetBounds = null;
346347
this.visible = null;
347348
this.computedStyles = null;
348349
this.styles = {};
@@ -500,19 +501,28 @@ NodeContainer.prototype.parseTextShadows = function() {
500501
NodeContainer.prototype.parseTransform = function() {
501502
var transformRegExp = /(matrix)\((.+)\)/;
502503
var transform = this.prefixedCss("transform");
503-
if (transform !== null && transform !== "none") {
504-
var matrix = parseMatrix(transform.match(transformRegExp));
505-
if (matrix) {
506-
return {
507-
origin: this.prefixedCss("transformOrigin"),
508-
matrix: matrix
509-
};
510-
}
504+
var matrix = parseMatrix(transform.match(transformRegExp));
505+
var offset = this.parseBounds();
506+
if (matrix) {
507+
var origin = this.prefixedCss("transformOrigin").split(" ").map(removePx).map(asFloat);
508+
origin[0] += offset.left;
509+
origin[1] += offset.top;
510+
511+
return {
512+
origin: origin,
513+
matrix: matrix
514+
};
511515
}
512-
return {
513-
origin: [0, 0],
514-
matrix: [1, 0, 0, 1, 0, 0]
515-
};
516+
};
517+
518+
NodeContainer.prototype.parseBounds = function() {
519+
return this.bounds || (this.bounds = this.hasTransform() ? offsetBounds(this.node) : getBounds(this.node));
520+
};
521+
522+
523+
NodeContainer.prototype.hasTransform = function() {
524+
var transform = this.prefixedCss("transform");
525+
return (transform !== null && transform !== "none" && transform !== "matrix(1, 0, 0, 1, 0, 0)");
516526
};
517527

518528
NodeContainer.prototype.TEXT_SHADOW_PROPERTY = /((rgba|rgb)\([^\)]+\)(\s-?\d+px){0,})/g;
@@ -627,6 +637,44 @@ function parseBackgrounds(backgroundImage) {
627637
return results;
628638
}
629639

640+
function removePx(str) {
641+
return str.replace("px", "");
642+
}
643+
644+
function asFloat(str) {
645+
return parseFloat(str);
646+
}
647+
648+
function getBounds(node) {
649+
if (node.getBoundingClientRect) {
650+
var clientRect = node.getBoundingClientRect();
651+
var isBody = node.nodeName === "BODY";
652+
var width = isBody ? node.scrollWidth : node.offsetWidth;
653+
return {
654+
top: clientRect.top,
655+
bottom: clientRect.bottom || (clientRect.top + clientRect.height),
656+
right: clientRect.left + width,
657+
left: clientRect.left,
658+
width: width,
659+
height: isBody ? node.scrollHeight : node.offsetHeight
660+
};
661+
}
662+
return {};
663+
}
664+
665+
function offsetBounds(node) {
666+
var parent = node.offsetParent ? offsetBounds(node.offsetParent) : {top: 0, left: 0};
667+
668+
return {
669+
top: node.offsetTop + parent.top,
670+
bottom: node.offsetTop + node.offsetHeight + parent.top,
671+
right: node.offsetLeft + parent.left + node.offsetWidth,
672+
left: node.offsetLeft + parent.left,
673+
width: node.offsetWidth,
674+
height: node.offsetHeight
675+
};
676+
}
677+
630678
function NodeParser(element, renderer, support, imageLoader, options) {
631679
log("Starting NodeParser");
632680
this.renderer = renderer;
@@ -763,7 +811,7 @@ NodeParser.prototype.newStackingContext = function(container, hasOwnStacking) {
763811

764812
NodeParser.prototype.createStackingContexts = function() {
765813
this.nodes.forEach(function(container) {
766-
if (isElement(container) && (this.isRootElement(container) || hasOpacity(container) || isPositionedForStacking(container) || this.isBodyWithTransparentRoot(container) || hasTransform(container))) {
814+
if (isElement(container) && (this.isRootElement(container) || hasOpacity(container) || isPositionedForStacking(container) || this.isBodyWithTransparentRoot(container) || container.hasTransform())) {
767815
this.newStackingContext(container, true);
768816
} else if (isElement(container) && ((isPositioned(container) && zIndex0(container)) || isInlineBlock(container) || isFloating(container))) {
769817
this.newStackingContext(container, false);
@@ -786,55 +834,33 @@ NodeParser.prototype.sortStackingContexts = function(stack) {
786834
stack.contexts.forEach(this.sortStackingContexts, this);
787835
};
788836

789-
NodeParser.prototype.parseBounds = function(nodeContainer) {
790-
return nodeContainer.bounds = this.getBounds(nodeContainer.node);
791-
};
792-
793-
NodeParser.prototype.getBounds = function(node) {
794-
if (node.getBoundingClientRect) {
795-
var clientRect = node.getBoundingClientRect();
796-
var isBody = node.nodeName === "BODY";
797-
var width = isBody ? node.scrollWidth : node.offsetWidth;
798-
return {
799-
top: clientRect.top,
800-
bottom: clientRect.bottom || (clientRect.top + clientRect.height),
801-
right: clientRect.left + width,
802-
left: clientRect.left,
803-
width: width,
804-
height: isBody ? node.scrollHeight : node.offsetHeight
805-
};
806-
}
807-
return {};
808-
};
809-
810837
NodeParser.prototype.parseTextBounds = function(container) {
811838
return function(text, index, textList) {
812-
if (container.parent.css("textDecoration") !== "none" || text.trim().length !== 0) {
813-
if (this.support.rangeBounds) {
839+
if (container.parent.css("textDecoration").substr(0, 4) !== "none" || text.trim().length !== 0) {
840+
if (this.support.rangeBounds && !container.parent.hasTransform()) {
814841
var offset = textList.slice(0, index).join("").length;
815842
return this.getRangeBounds(container.node, offset, text.length);
816843
} else if (container.node && typeof(container.node.data) === "string") {
817844
var replacementNode = container.node.splitText(text.length);
818-
var bounds = this.getWrapperBounds(container.node);
845+
var bounds = this.getWrapperBounds(container.node, container.parent.hasTransform());
819846
container.node = replacementNode;
820847
return bounds;
821848
}
822-
} else if (!this.support.rangeBounds) {
849+
} else if(!this.support.rangeBounds || container.parent.hasTransform()){
823850
container.node = container.node.splitText(text.length);
824851
}
825852
return {};
826853
};
827854
};
828855

829-
NodeParser.prototype.getWrapperBounds = function(node) {
830-
var wrapper = node.ownerDocument.createElement('wrapper');
856+
NodeParser.prototype.getWrapperBounds = function(node, transform) {
857+
var wrapper = node.ownerDocument.createElement('html2canvaswrapper');
831858
var parent = node.parentNode,
832859
backupText = node.cloneNode(true);
833860

834861
wrapper.appendChild(node.cloneNode(true));
835862
parent.replaceChild(wrapper, node);
836-
837-
var bounds = this.getBounds(wrapper);
863+
var bounds = transform ? offsetBounds(wrapper) : getBounds(wrapper);
838864
parent.replaceChild(backupText, wrapper);
839865
return bounds;
840866
};
@@ -846,6 +872,8 @@ NodeParser.prototype.getRangeBounds = function(node, offset, length) {
846872
return range.getBoundingClientRect();
847873
};
848874

875+
function ClearTransform() {}
876+
849877
NodeParser.prototype.parse = function(stack) {
850878
// http://www.w3.org/TR/CSS21/visuren.html#z-index
851879
var negativeZindex = stack.contexts.filter(negativeZIndex); // 2. the child stacking contexts with negative stack levels (most negative first).
@@ -862,13 +890,16 @@ NodeParser.prototype.parse = function(stack) {
862890
this.renderQueue.push(container);
863891
if (isStackingContext(container)) {
864892
this.parse(container);
893+
this.renderQueue.push(new ClearTransform());
865894
}
866895
}, this);
867896
};
868897

869898
NodeParser.prototype.paint = function(container) {
870899
try {
871-
if (isTextNode(container)) {
900+
if (container instanceof ClearTransform) {
901+
this.renderer.ctx.restore();
902+
} else if (isTextNode(container)) {
872903
this.paintText(container);
873904
} else {
874905
this.paintNode(container);
@@ -881,13 +912,12 @@ NodeParser.prototype.paint = function(container) {
881912
NodeParser.prototype.paintNode = function(container) {
882913
if (isStackingContext(container)) {
883914
this.renderer.setOpacity(container.opacity);
884-
var transform = container.parseTransform();
885-
if (transform) {
886-
this.renderer.setTransform(transform);
915+
this.renderer.ctx.save();
916+
if (container.hasTransform()) {
917+
this.renderer.setTransform(container.parseTransform());
887918
}
888919
}
889-
890-
var bounds = this.parseBounds(container);
920+
var bounds = container.parseBounds();
891921
var borderData = this.parseBorders(container);
892922
this.renderer.clip(borderData.clip, function() {
893923
this.renderer.renderBackground(container, bounds, borderData.borders.map(getWidth));
@@ -1254,11 +1284,6 @@ function hasOpacity(container) {
12541284
return container.css("opacity") < 1;
12551285
}
12561286

1257-
function hasTransform(container) {
1258-
var transform = container.prefixedCss("transform");
1259-
return transform !== null && transform !== "none";
1260-
}
1261-
12621287
function bind(callback, context) {
12631288
return function() {
12641289
return callback.apply(context, arguments);
@@ -1516,8 +1541,8 @@ CanvasRenderer.prototype.setOpacity = function(opacity) {
15161541

15171542
CanvasRenderer.prototype.setTransform = function(transform) {
15181543
this.ctx.translate(transform.origin[0], transform.origin[1]);
1519-
this.ctx.setTransform.apply(this.ctx, transform.matrix);
1520-
this.ctx.translate(transform.origin[0], transform.origin[1]);
1544+
this.ctx.transform.apply(this.ctx, transform.matrix);
1545+
this.ctx.translate(-transform.origin[0], -transform.origin[1]);
15211546
};
15221547

15231548
CanvasRenderer.prototype.setVariable = function(property, value) {
@@ -1588,12 +1613,12 @@ StackingContext.prototype.getParentStack = function(context) {
15881613
return parentStack ? (parentStack.ownStacking ? parentStack : parentStack.getParentStack(context)) : context.stack;
15891614
};
15901615

1591-
function Support() {
1592-
this.rangeBounds = this.testRangeBounds();
1616+
function Support(document) {
1617+
this.rangeBounds = this.testRangeBounds(document);
15931618
this.cors = this.testCORS();
15941619
}
15951620

1596-
Support.prototype.testRangeBounds = function() {
1621+
Support.prototype.testRangeBounds = function(document) {
15971622
var range, testElement, rangeBounds, rangeHeight, support = false;
15981623

15991624
if (document.createRange) {

build/html2canvas.min.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/core.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ function renderDocument(document, options, windowWidth, windowHeight) {
2828
document.querySelector(selector).removeAttribute(html2canvasNodeAttribute);
2929
var clonedWindow = container.contentWindow;
3030
var node = clonedWindow.document.querySelector(selector);
31-
var support = new Support();
31+
var support = new Support(clonedWindow.document);
3232
var imageLoader = new ImageLoader(options, support);
33-
var bounds = NodeParser.prototype.getBounds(node);
33+
var bounds = getBounds(node);
3434
var width = options.type === "view" ? Math.min(bounds.width, windowWidth) : documentWidth();
3535
var height = options.type === "view" ? Math.min(bounds.height, windowHeight) : documentHeight();
3636
var renderer = new CanvasRenderer(width, height, imageLoader);

src/nodecontainer.js

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ function NodeContainer(node, parent) {
33
this.parent = parent;
44
this.stack = null;
55
this.bounds = null;
6+
this.offsetBounds = null;
67
this.visible = null;
78
this.computedStyles = null;
89
this.styles = {};
@@ -160,19 +161,28 @@ NodeContainer.prototype.parseTextShadows = function() {
160161
NodeContainer.prototype.parseTransform = function() {
161162
var transformRegExp = /(matrix)\((.+)\)/;
162163
var transform = this.prefixedCss("transform");
163-
if (transform !== null && transform !== "none") {
164-
var matrix = parseMatrix(transform.match(transformRegExp));
165-
if (matrix) {
166-
return {
167-
origin: this.prefixedCss("transformOrigin"),
168-
matrix: matrix
169-
};
170-
}
164+
var matrix = parseMatrix(transform.match(transformRegExp));
165+
var offset = this.parseBounds();
166+
if (matrix) {
167+
var origin = this.prefixedCss("transformOrigin").split(" ").map(removePx).map(asFloat);
168+
origin[0] += offset.left;
169+
origin[1] += offset.top;
170+
171+
return {
172+
origin: origin,
173+
matrix: matrix
174+
};
171175
}
172-
return {
173-
origin: [0, 0],
174-
matrix: [1, 0, 0, 1, 0, 0]
175-
};
176+
};
177+
178+
NodeContainer.prototype.parseBounds = function() {
179+
return this.bounds || (this.bounds = this.hasTransform() ? offsetBounds(this.node) : getBounds(this.node));
180+
};
181+
182+
183+
NodeContainer.prototype.hasTransform = function() {
184+
var transform = this.prefixedCss("transform");
185+
return (transform !== null && transform !== "none" && transform !== "matrix(1, 0, 0, 1, 0, 0)");
176186
};
177187

178188
NodeContainer.prototype.TEXT_SHADOW_PROPERTY = /((rgba|rgb)\([^\)]+\)(\s-?\d+px){0,})/g;
@@ -286,3 +296,41 @@ function parseBackgrounds(backgroundImage) {
286296
appendResult();
287297
return results;
288298
}
299+
300+
function removePx(str) {
301+
return str.replace("px", "");
302+
}
303+
304+
function asFloat(str) {
305+
return parseFloat(str);
306+
}
307+
308+
function getBounds(node) {
309+
if (node.getBoundingClientRect) {
310+
var clientRect = node.getBoundingClientRect();
311+
var isBody = node.nodeName === "BODY";
312+
var width = isBody ? node.scrollWidth : node.offsetWidth;
313+
return {
314+
top: clientRect.top,
315+
bottom: clientRect.bottom || (clientRect.top + clientRect.height),
316+
right: clientRect.left + width,
317+
left: clientRect.left,
318+
width: width,
319+
height: isBody ? node.scrollHeight : node.offsetHeight
320+
};
321+
}
322+
return {};
323+
}
324+
325+
function offsetBounds(node) {
326+
var parent = node.offsetParent ? offsetBounds(node.offsetParent) : {top: 0, left: 0};
327+
328+
return {
329+
top: node.offsetTop + parent.top,
330+
bottom: node.offsetTop + node.offsetHeight + parent.top,
331+
right: node.offsetLeft + parent.left + node.offsetWidth,
332+
left: node.offsetLeft + parent.left,
333+
width: node.offsetWidth,
334+
height: node.offsetHeight
335+
};
336+
}

0 commit comments

Comments
 (0)