From a313524aa40cd83e371d3ecd09d1a77bec29a9e7 Mon Sep 17 00:00:00 2001 From: Niklas von Hertzen Date: Thu, 3 Jan 2013 23:15:06 +0200 Subject: [PATCH 1/2] refactored renderer --- src/Renderer.js | 6 +- src/Util.js | 1 + src/renderers/Canvas.js | 345 +++++++++++++++------------------------- 3 files changed, 129 insertions(+), 223 deletions(-) diff --git a/src/Renderer.js b/src/Renderer.js index edf4d90..da1e46b 100644 --- a/src/Renderer.js +++ b/src/Renderer.js @@ -22,7 +22,7 @@ _html2canvas.Renderer = function(parseQueue, options){ stackValues.forEach(function(zValue) { var index; - + subStacks.some(function(stack, i){ index = i; return (stack.zindex === zValue); @@ -48,11 +48,11 @@ _html2canvas.Renderer = function(parseQueue, options){ throw new Error("Unknown renderer"); } - if ( typeof renderer._create !== "function" ) { + if ( typeof renderer !== "function" ) { throw new Error("Invalid renderer defined"); } return renderer; } - return getRenderer(options.renderer)._create(parseQueue, options, document, createRenderQueue(parseQueue), _html2canvas); + return getRenderer(options.renderer)(parseQueue, options, document, createRenderQueue(parseQueue), _html2canvas); }; diff --git a/src/Util.js b/src/Util.js index f8d59ec..64f724b 100644 --- a/src/Util.js +++ b/src/Util.js @@ -5,6 +5,7 @@ window.html2canvas = function(elements, opts) { // general logging: false, elements: elements, + background: "#fff", // preload options proxy: "", diff --git a/src/renderers/Canvas.js b/src/renderers/Canvas.js index 78e8d7e..134ad4a 100644 --- a/src/renderers/Canvas.js +++ b/src/renderers/Canvas.js @@ -1,233 +1,138 @@ -_html2canvas.Renderer.Canvas = function( options ) { +_html2canvas.Renderer.Canvas = function(options) { options = options || {}; var doc = document, - canvas = options.canvas || doc.createElement('canvas'), - usingFlashcanvas = false, - _createCalled = false, - canvasReadyToDraw = false, - methods, - flashMaxSize = 2880; // flash bitmap limited to 2880x2880px // http://stackoverflow.com/questions/2033792/argumenterror-error-2015-invalid-bitmapdata + safeImages = [], + testCanvas = document.createElement("canvas"), + testctx = testCanvas.getContext("2d"), + canvas = options.canvas || doc.createElement('canvas'); - if (canvas.getContext){ - h2clog("html2canvas: Renderer: using canvas renderer"); - canvasReadyToDraw = true; - } else if ( options.flashcanvas !== undefined ){ - usingFlashcanvas = true; - h2clog("html2canvas: Renderer: canvas not available, using flashcanvas"); - var script = doc.createElement("script"); - script.src = options.flashcanvas; - - script.onload = (function(script, func){ - var intervalFunc; - - if (script.onload === undefined) { - // IE lack of support for script onload - - if( script.onreadystatechange !== undefined ) { - - intervalFunc = function() { - if (script.readyState !== "loaded" && script.readyState !== "complete") { - window.setTimeout( intervalFunc, 250 ); - - } else { - // it is loaded - func(); - - } - - }; - - window.setTimeout( intervalFunc, 250 ); - - } else { - h2clog("html2canvas: Renderer: Can't track when flashcanvas is loaded"); - } - - } else { - return func; - } - - })(script, function(){ - - if (typeof window.FlashCanvas !== "undefined") { - h2clog("html2canvas: Renderer: Flashcanvas initialized"); - window.FlashCanvas.initElement( canvas ); - - canvasReadyToDraw = true; - if ( _createCalled !== false ) { - methods._create.apply( null, _createCalled ); - } - } + function createShape(ctx, args) { + ctx.beginPath(); + args.forEach(function(arg) { + ctx[arg.name].apply(ctx, arg['arguments']); }); - - doc.body.appendChild( script ); - + ctx.closePath(); } - methods = { - _create: function( zStack, options, doc, queue, _html2canvas ) { - - if ( !canvasReadyToDraw ) { - _createCalled = arguments; - return canvas; + function safeImage(item) { + if (safeImages.indexOf(item['arguments'][0].src ) === -1) { + testctx.drawImage(item['arguments'][0], 0, 0); + try { + testctx.getImageData(0, 0, 1, 1); + } catch(e) { + testCanvas = doc.createElement("canvas"); + testctx = testCanvas.getContext("2d"); + return false; } - - var ctx = canvas.getContext("2d"), - storageContext, - i, - queueLen, - a, - newCanvas, - bounds, - testCanvas = document.createElement("canvas"), - hasCTX = ( testCanvas.getContext !== undefined ), - storageLen, - renderItem, - testctx = ( hasCTX ) ? testCanvas.getContext("2d") : {}, - safeImages = [], - fstyle; - - canvas.width = canvas.style.width = (!usingFlashcanvas) ? options.width || zStack.ctx.width : Math.min(flashMaxSize, (options.width || zStack.ctx.width) ); - canvas.height = canvas.style.height = (!usingFlashcanvas) ? options.height || zStack.ctx.height : Math.min(flashMaxSize, (options.height || zStack.ctx.height) ); - - fstyle = ctx.fillStyle; - ctx.fillStyle = (zStack.backgroundColor === "transparent" || zStack.backgroundColor === "rgba(0, 0, 0, 0)") ? "#fff" : zStack.backgroundColor; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.fillStyle = fstyle; - - var createShape = function(args) { - ctx.beginPath(); - args.forEach(function(arg) { - ctx[arg.name].apply(ctx, arg['arguments']); - }); - ctx.closePath(); - }; - - if ( options.svgRendering && zStack.svgRender !== undefined ) { - // TODO: enable async rendering to support this - ctx.drawImage( zStack.svgRender, 0, 0 ); - } else { - for ( i = 0, queueLen = queue.length; i < queueLen; i+=1 ) { - - storageContext = queue.splice(0, 1)[0]; - storageContext.canvasPosition = storageContext.canvasPosition || {}; - - //this.canvasRenderContext(storageContext,parentctx); - - // set common settings for canvas - ctx.textBaseline = "bottom"; - - if (storageContext.clip){ - ctx.save(); - ctx.beginPath(); - // console.log(storageContext); - ctx.rect(storageContext.clip.left, storageContext.clip.top, storageContext.clip.width, storageContext.clip.height); - ctx.clip(); - - } - - if (storageContext.ctx.storage){ - - for (a = 0, storageLen = storageContext.ctx.storage.length; a < storageLen; a+=1){ - - renderItem = storageContext.ctx.storage[a]; - - - switch(renderItem.type){ - case "variable": - ctx[renderItem.name] = renderItem['arguments']; - break; - case "function": - if (renderItem.name === "fillRect") { - if (!usingFlashcanvas || renderItem['arguments'][0] + renderItem['arguments'][2] < flashMaxSize && renderItem['arguments'][1] + renderItem['arguments'][3] < flashMaxSize) { - ctx.fillRect.apply( ctx, renderItem['arguments'] ); - } - } else if (renderItem.name === "createPattern") { - ctx.fillStyle = ctx.createPattern(renderItem['arguments'][0], "repeat"); - } else if (renderItem.name === "drawShape") { - createShape(renderItem['arguments']); - } else if (renderItem.name === "fillText") { - if (!usingFlashcanvas || renderItem['arguments'][1] < flashMaxSize && renderItem['arguments'][2] < flashMaxSize) { - ctx.fillText.apply( ctx, renderItem['arguments'] ); - } - } else if (renderItem.name === "drawImage") { - - if (renderItem['arguments'][8] > 0 && renderItem['arguments'][7] > 0) { - if ( hasCTX && options.taintTest ) { - if ( safeImages.indexOf( renderItem['arguments'][ 0 ].src ) === -1 ) { - testctx.drawImage( renderItem['arguments'][ 0 ], 0, 0 ); - try { - testctx.getImageData( 0, 0, 1, 1 ); - } catch(e) { - testCanvas = doc.createElement("canvas"); - testctx = testCanvas.getContext("2d"); - continue; - } - - safeImages.push( renderItem['arguments'][ 0 ].src ); - - } - } - ctx.drawImage.apply( ctx, renderItem['arguments'] ); - } - } else { - ctx[renderItem.name].apply(ctx, renderItem['arguments']); - } - - - break; - default: - - } - - } - - } - if (storageContext.clip){ - ctx.restore(); - } - - } - } - - h2clog("html2canvas: Renderer: Canvas renderer done - returning canvas obj"); - - queueLen = options.elements.length; - - if (queueLen === 1) { - if (typeof options.elements[ 0 ] === "object" && options.elements[ 0 ].nodeName !== "BODY" && usingFlashcanvas === false) { - // crop image to the bounds of selected (single) element - bounds = _html2canvas.Util.Bounds( options.elements[ 0 ] ); - newCanvas = doc.createElement('canvas'); - newCanvas.width = bounds.width; - newCanvas.height = bounds.height; - ctx = newCanvas.getContext("2d"); - - ctx.drawImage( canvas, bounds.left, bounds.top, bounds.width, bounds.height, 0, 0, bounds.width, bounds.height ); - canvas = null; - return newCanvas; - } - } /*else { - // TODO clip and resize multiple elements - - for ( i = 0; i < queueLen; i+=1 ) { - if (options.elements[ i ] instanceof Element) { - - } - - } - } - */ - - - - return canvas; + safeImages.push(item['arguments'][0].src); } + return true; + } + + function isTransparent(backgroundColor) { + return (backgroundColor === "transparent" || backgroundColor === "rgba(0, 0, 0, 0)"); + } + + function renderItem(ctx, item) { + switch(item.type){ + case "variable": + ctx[item.name] = item['arguments']; + break; + case "function": + if (item.name === "createPattern") { + if (item['arguments'][0].width > 0 && item['arguments'][0].height > 0) { + try { + ctx.fillStyle = ctx.createPattern(item['arguments'][0], "repeat"); + } + catch(e) { + h2clog("html2canvas: Renderer: Error creating pattern", e.message); + } + } + } else if (item.name === "drawShape") { + createShape(ctx, item['arguments']); + } else if (item.name === "drawImage") { + if (item['arguments'][8] > 0 && item['arguments'][7] > 0) { + if (options.taintTest || (options.taintTest && safeImage(item))) { + ctx.drawImage.apply( ctx, item['arguments'] ); + } + } + } else { + ctx[item.name].apply(ctx, item['arguments']); + } + break; + } + } + + return function(zStack, options, doc, queue, _html2canvas) { + + var ctx = canvas.getContext("2d"), + storageContext, + i, + queueLen, + newCanvas, + bounds, + fstyle; + + canvas.width = canvas.style.width = options.width || zStack.ctx.width; + canvas.height = canvas.style.height = options.height || zStack.ctx.height; + + fstyle = ctx.fillStyle; + ctx.fillStyle = (isTransparent(zStack.backgroundColor) && options.background !== undefined) ? options.background : zStack.backgroundColor; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = fstyle; + + + if ( options.svgRendering && zStack.svgRender !== undefined ) { + // TODO: enable async rendering to support this + ctx.drawImage( zStack.svgRender, 0, 0 ); + } else { + for ( i = 0, queueLen = queue.length; i < queueLen; i+=1 ) { + storageContext = queue.splice(0, 1)[0]; + storageContext.canvasPosition = storageContext.canvasPosition || {}; + + // set common settings for canvas + ctx.textBaseline = "bottom"; + + if (storageContext.clip){ + ctx.save(); + ctx.beginPath(); + // console.log(storageContext); + ctx.rect(storageContext.clip.left, storageContext.clip.top, storageContext.clip.width, storageContext.clip.height); + ctx.clip(); + } + + if (storageContext.ctx.storage) { + storageContext.ctx.storage.forEach(renderItem.bind(null, ctx)); + } + + if (storageContext.clip){ + ctx.restore(); + } + } + } + + h2clog("html2canvas: Renderer: Canvas renderer done - returning canvas obj"); + + queueLen = options.elements.length; + + if (queueLen === 1) { + if (typeof options.elements[0] === "object" && options.elements[0].nodeName !== "BODY") { + // crop image to the bounds of selected (single) element + bounds = _html2canvas.Util.Bounds(options.elements[0]); + newCanvas = doc.createElement('canvas'); + newCanvas.width = bounds.width; + newCanvas.height = bounds.height; + ctx = newCanvas.getContext("2d"); + + ctx.drawImage(canvas, bounds.left, bounds.top, bounds.width, bounds.height, 0, 0, bounds.width, bounds.height); + canvas = null; + return newCanvas; + } + } + + return canvas; }; - - return methods; - }; \ No newline at end of file From 6d29cc5df3c97551209a4d9ab271a22d0c648750 Mon Sep 17 00:00:00 2001 From: Niklas von Hertzen Date: Fri, 4 Jan 2013 23:47:59 +0200 Subject: [PATCH 2/2] chinese word rendering --- src/Parse.js | 10 +++++++++ src/Util.js | 2 +- tests/cases/text/chinese.html | 41 +++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 tests/cases/text/chinese.html diff --git a/src/Parse.js b/src/Parse.js index 103ade9..808a5e8 100644 --- a/src/Parse.js +++ b/src/Parse.js @@ -170,6 +170,16 @@ _html2canvas.Parse = function (images, options) { metrics = setTextVariables(ctx, el, textDecoration, color); + if (options.chinese) { + textList.forEach(function(word, index) { + if (/.*[\u4E00-\u9FA5].*$/.test(word)) { + word = word.split(""); + word.unshift(index, 1) + textList.splice.apply(textList, word); + } + }); + } + textList.forEach(function(text, index) { var bounds = getTextBounds(state, text, textDecoration, (index < textList.length - 1)); if (bounds) { diff --git a/src/Util.js b/src/Util.js index 64f724b..74b5f27 100644 --- a/src/Util.js +++ b/src/Util.js @@ -18,10 +18,10 @@ window.html2canvas = function(elements, opts) { ignoreElements: "IFRAME|OBJECT|PARAM", useOverflow: true, letterRendering: false, + chinese: false, // render options - flashcanvas: undefined, // path to flashcanvas width: null, height: null, taintTest: true, // do a taint test with all images before applying to canvas diff --git a/tests/cases/text/chinese.html b/tests/cases/text/chinese.html new file mode 100644 index 0000000..a9199cd --- /dev/null +++ b/tests/cases/text/chinese.html @@ -0,0 +1,41 @@ + + + + Chinese text + + + + + + + +
+

    注    释 +

    〔1〕 见本书第一卷《实践论》注〔6〕。 +

    〔2〕 见列宁《党的组织和党的出版物》。列宁在这篇论文中说:“这将是自由的写作,因为把一批又一批新生力量吸引到写作队伍中来的,不是私利贪欲,也不是名誉地位,而是社会主义思想和对劳动人民的同情。这将是自由的写作,因为它不是为饱食终日的贵妇人服务,不是为百无聊赖、胖得发愁的‘一万个上层分子’服务,而是为千千万万劳动人民,为这些国家的精华、国家的力量、国家的未来服务。这将是自由的写作,它要用社会主义无产阶级的经验和生气勃勃的工作去丰富人类革命思想的最新成就,它要使过去的经验(从原始空想的社会主义发展而成的科学社会主义)和现在的经验(工人同志们当前的斗争)之间经常发生相互作用。”(《列宁全集》第12卷,人民出版社1987年版,第96—97页) +

    〔3〕 梁实秋(一九○三——一九八七),北京人。新月社主要成员。先后在复旦大学、北京大学等校任教。曾写过一些文艺评论,长时期致力于文学翻译工作和散文的写作。鲁迅对梁实秋的批评,见《三闲集·新月社批评家的任务》、《二心集·“硬译”与“文学的阶级性”》等文。(《鲁迅全集》第4卷,人民文学出版社1981年版,第159、195—212页) +

    〔4〕 周作人(一八八五——一九六七),浙江绍兴人。曾在北京大学、燕京大学等校任教。五四运动时从事新文学写作。他的著述很多,有大量的散文集、文学专著和翻译作品。张资平(一八九三——一九五九),广东梅县人。他写过很多小说,曾在暨南大学、大夏大学兼任教职。周作人、张资平于一九三八年和一九三九年先后在北平、上海依附侵略中国的日本占领者。 +

    〔5〕 见鲁迅《二心集·对于左翼作家联盟的意见》(《鲁迅全集》第4卷,人民文学出版社1981年版,第237—238页)。 +

    〔6〕 参见鲁迅《且介亭杂文末编·附集·死》(《鲁迅全集》第6卷,人民文学出版社1981年版,第612页)。 +

    〔7〕 “小放牛”是中国一出传统的小歌舞剧。全剧只有两个角色,男角是牧童,女角是乡村小姑娘,以互相对唱的方式表现剧的内容。抗日战争初期,革命的文艺工作者利用这个歌舞剧的形式,变动其原来的词句,宣传抗日,一时颇为流行。 +

    〔8〕 “人、手、口、刀、牛、羊”是笔画比较简单的汉字,旧时一些小学国语读本把这几个字编在第一册的最初几课里。 +

    〔9〕 “阳春白雪”和“下里巴人”,都是公元前三世纪楚国的歌曲。“阳春白雪”是供少数人欣赏的较高级的歌曲;“下里巴人”是流传很广的民间歌曲。《文选·宋玉对楚王问》记载一个故事,说有人在楚都唱歌,唱“阳春白雪”时,“国中属而和者(跟着唱的),不过数十人”;但唱“下里巴人”时,“国中属而和者数千人”。 +

    〔10〕 见列宁《党的组织和党的出版物》。列宁在这篇论文中说:“写作事业应当成为整个无产阶级事业的一部分,成为由整个工人阶级的整个觉悟的先锋队所开动的一部巨大的社会民主主义机器的‘齿轮和螺丝钉’。”(《列宁全集》第12卷,人民出版社1987年版,第93页) +

    〔11〕 亭子间是上海里弄房子中的一种小房间,位置在房子后部的楼梯中侧,狭小黑暗,因此租金比较低廉。解放以前,贫苦的作家、艺术家、知识分子和机关小职员,多半租这种房间居住。 +

    〔12〕 见本书第二卷《和中央社、扫荡报、新民报三记者的谈话》注〔3〕。 +

    〔13〕 法捷耶夫(一九○一——一九五六),苏联名作家。他所作的小说《毁灭》于一九二七年出版,内容是描写苏联国内战争时期由苏联远东滨海边区工人、农民和革命知识分子所组成的一支游击队同国内反革命白卫军以及日本武装干涉军进行斗争的故事。这部小说曾由鲁迅译为汉文。 +

    〔14〕 见鲁迅《集外集·自嘲》(《鲁迅全集》第7卷,人民文学出版社1981年版,第147页)。

+
+ + \ No newline at end of file