From 4e5ea6fa8b25b103e2ca39140debb5cc9824cea6 Mon Sep 17 00:00:00 2001 From: mkwiser Date: Sun, 31 Aug 2014 01:48:32 +0800 Subject: [PATCH] add glyf2canvas --- demo/css/glyf.css | 12 +- demo/css/glyf.less | 14 +-- demo/glyfcanvas.html | 35 ++++++ demo/{glyf.html => glyfsvg.html} | 2 +- demo/js/glyfcanvas.js | 124 ++++++++++++++++++ demo/js/{glyf.js => glyfsvg.js} | 40 +++--- demo/js/quadraticBezier.js | 13 +- src/editor/common/glyf2canvas.js | 208 +++++++++++++++++++++++++++++++ src/ttf/glyf2svg.js | 49 +++++--- 9 files changed, 434 insertions(+), 63 deletions(-) create mode 100644 demo/glyfcanvas.html rename demo/{glyf.html => glyfsvg.html} (95%) create mode 100644 demo/js/glyfcanvas.js rename demo/js/{glyf.js => glyfsvg.js} (78%) create mode 100644 src/editor/common/glyf2canvas.js diff --git a/demo/css/glyf.css b/demo/css/glyf.css index 0dc22f4..68a3f3b 100644 --- a/demo/css/glyf.css +++ b/demo/css/glyf.css @@ -45,21 +45,11 @@ body { .font-list li.selected { background: #ECECEC; } -.svg-view { - width: 500px; - height: 500px; - background: #F0F0F0; -} .glyf { - width: 100%; + background: #F0F0F0; } .glyf .path { fill: green; stroke: none; stroke-width: 10px; } -.boundary { - fill: none; - stroke-width: 1px; - stroke: black; -} diff --git a/demo/css/glyf.less b/demo/css/glyf.less index 303064a..ecadc9d 100644 --- a/demo/css/glyf.less +++ b/demo/css/glyf.less @@ -38,23 +38,11 @@ body { } } -.svg-view { - width: 500px; - height: 500px; - background: #F0F0F0; -} - .glyf { - width: 100%; + background: #F0F0F0; .path { fill: green; stroke: none; stroke-width: 10px; } -} - -.boundary { - fill: none; - stroke-width: 1px; - stroke: black; } \ No newline at end of file diff --git a/demo/glyfcanvas.html b/demo/glyfcanvas.html new file mode 100644 index 0000000..b444f28 --- /dev/null +++ b/demo/glyfcanvas.html @@ -0,0 +1,35 @@ + + + + + glyf查看 + + + + + + + + +
+ +
+ + + + + + + + + \ No newline at end of file diff --git a/demo/glyf.html b/demo/glyfsvg.html similarity index 95% rename from demo/glyf.html rename to demo/glyfsvg.html index aab6d87..8eab482 100644 --- a/demo/glyf.html +++ b/demo/glyfsvg.html @@ -28,7 +28,7 @@
diff --git a/demo/js/glyfcanvas.js b/demo/js/glyfcanvas.js new file mode 100644 index 0000000..20cf59c --- /dev/null +++ b/demo/js/glyfcanvas.js @@ -0,0 +1,124 @@ +/** + * @file glyf.js + * @author mengke01 + * @date + * @description + * glyf canvas 绘制 + */ + +define( + function(require) { + + var ttfreader = require('editor/ttf/ttfreader'); + var TTF = require('editor/ttf/ttf'); + var ttf2base64 = require('editor/ttf/ttf2base64'); + var ajaxBinaryFile = require('editor/common/ajaxBinaryFile'); + var glyf2svg = require('editor/ttf/glyf2svg'); + var setFontface = require('./setFontface'); + var glyf2canvas = require('editor/editor/common/glyf2canvas'); + var ttf = null; + + // 设置字体 + function setFont(arrayBuffer) { + var base64 = ttf2base64(arrayBuffer); + setFontface('truetype', base64, 'font-face'); + } + + + // 查看ttf glyf + function showTTFGlyf(ttfData) { + + ttf = new TTF(ttfData); + var chars = ttf.chars(); + + var str = ''; + // 获取unicode字符 + chars.forEach(function(item) { + str += '
  • ' + + ''+ String.fromCharCode(item) +'' + + (item > 255 ? '\\u' + Number(item).toString(16) : item) + +'
  • '; + }); + $('#font-list').html(str); + $('#font-list li:nth-child(4)').click(); + } + + function showGlyf(charcode) { + var glyf = ttf.getCharGlyf(charcode); + var canvas = $('#glyf-canvas').get(0); + var ctx = canvas.getContext('2d'); + + // 调整大小 + var width = glyf.xMax - glyf.xMin; + var height = glyf.yMax - glyf.yMin; + var scale = 1; + if(ttf.ttf.head.unitsPerEm > 512) { + scale = 512 / ttf.ttf.head.unitsPerEm; + width = width * scale; + height = height * scale; + } + canvas.width = width; + canvas.height = height; + ctx.clearRect(0, 0, width, height); + glyf2canvas(glyf, ctx, { + stroke: 0, + scale: scale, + strokeStyle: 'green', + fillStyle: 'green' + }); + } + + + function onUpFileChange(e) { + var file = e.target.files[0]; + var reader = new FileReader(); + reader.onload = function(e) { + var binaryData = e.target.result; + setFont(binaryData); + + var ttfData = new ttfreader().read(binaryData); + showTTFGlyf(ttfData); + } + + reader.onerror = function(e) { + console.error(e); + }; + reader.readAsArrayBuffer(file); + } + + var entry = { + + /** + * 初始化 + */ + init: function() { + var upFile = document.getElementById('upload-file'); + upFile.addEventListener('change', onUpFileChange); + + ajaxBinaryFile({ + url: '../font/baiduHealth.ttf', + onSuccess: function(binaryData) { + setFont(binaryData); + + var ttfData = new ttfreader().read(binaryData); + showTTFGlyf(ttfData); + }, + onError: function() { + console.error('error read file'); + } + }); + + + $('#font-list').delegate('li', 'click', function(e) { + $('#font-list li').removeClass('selected'); + $(this).addClass('selected'); + showGlyf(+$(this).attr('data-code')); + }); + } + }; + + entry.init(); + + return entry; + } +); diff --git a/demo/js/glyf.js b/demo/js/glyfsvg.js similarity index 78% rename from demo/js/glyf.js rename to demo/js/glyfsvg.js index cba6ed1..b2b445c 100644 --- a/demo/js/glyf.js +++ b/demo/js/glyfsvg.js @@ -1,5 +1,5 @@ /** - * @file glyf.js + * @file glyfsvg.js * @author mengke01 * @date * @description @@ -47,26 +47,36 @@ define( function showGlyf(charcode) { var tpl = '' - + '' - + ' ' - + '' + + '' + + ' ' + '' + '' + ''; var svg = $(tpl); var glyf = ttf.getCharGlyf(charcode); - var path = glyf2svg(glyf); - if (path) { - var scale = 1000 / ttf.ttf.head.unitsPerEm; - var boundary = '' - + glyf.xMin + ',' + glyf.yMin + ' ' - + glyf.xMin + ',' + glyf.yMax + ' ' - + glyf.xMax + ',' + glyf.yMax + ' ' - + glyf.xMax + ',' + glyf.yMin + ' ' - + glyf.xMin + ',' + glyf.yMin + ' '; - svg.find(".boundary").attr('points', boundary).attr('transform', 'scale(' + scale + ',' + scale + ')'); - svg.find(".path").attr('d', path).attr('transform', 'scale(' + scale + ',' + scale + ')'); + // 调整大小 + var width = glyf.xMax - glyf.xMin; + var height = glyf.yMax - glyf.yMin; + var scale = 1; + + if(ttf.ttf.head.unitsPerEm > 512) { + scale = 512 / ttf.ttf.head.unitsPerEm; + width = width * scale; + height = height * scale; + } + + var path = glyf2svg(glyf, { + scale: scale + }); + + if (path) { + svg.css({ + width: width, + height: height + }); + svg.attr('viewbox', '0 0 ' + width + ' ' + height); + svg.find(".path").attr('d', path); } $('#svg-view').html(svg); diff --git a/demo/js/quadraticBezier.js b/demo/js/quadraticBezier.js index 1cefdff..029dedd 100644 --- a/demo/js/quadraticBezier.js +++ b/demo/js/quadraticBezier.js @@ -17,15 +17,16 @@ define( * 初始化 */ init: function() { - var ctx = document.getElementById('canvas').getContext('2d'); - var width = ctx.offsetWidth; - var height = ctx.offsetHeight; + var canvas = $('#canvas').get(0); + var ctx = canvas.getContext('2d'); + var width = canvas.offsetWidth; + var height = canvas.offsetHeight; var points = []; [0, 1, 2].forEach(function(i) { var p = { - x: Math.floor(Math.random() * 400 + 50), - y: Math.floor(Math.random() * 400 + 50) + x: Math.floor(Math.random() * (width - 100) + 50), + y: Math.floor(Math.random() * (height - 100) + 50) } points[i] = p; $($('.point').get(i)).css({ @@ -62,7 +63,7 @@ define( } function draw() { - ctx.clearRect(0, 0, 500, 500); + ctx.clearRect(0, 0, width, height); //绘制2次贝塞尔曲线 ctx.beginPath(); ctx.strokeStyle='black'; diff --git a/src/editor/common/glyf2canvas.js b/src/editor/common/glyf2canvas.js new file mode 100644 index 0000000..7e013fe --- /dev/null +++ b/src/editor/common/glyf2canvas.js @@ -0,0 +1,208 @@ +/** + * @file glyf2canvas.js + * @author mengke01 + * @date + * @description + * glyf 的canvas绘制 + */ + + +define( + function(require) { + + /** + * glyf canvas绘制 + * + * @param {Object} glyf glyf数据 + * @param {Context} ctx canvas的context + * @param {Object} options 绘制参数 + */ + function glyf2canvas(glyf, ctx, options){ + + if(!glyf) { + return; + } + + options = options || {}; + + if(options.stroke) { + ctx.strokeWidth = options.strokeWidth || 1; + ctx.strokeStyle = options.strokeStyle || 'black'; + } + else { + ctx.fillStyle = options.fillStyle || 'black'; + } + + + // 对轮廓进行反向,以及坐标系调整,取整 + var xOffset = -glyf.xMin; + var yOffset = -glyf.yMin; + var middleYx2 = glyf.yMax + glyf.yMin; + var scale = options.scale || 1; + var coordinates = []; + + glyf.coordinates.forEach(function(p) { + coordinates.push({ + x: scale * (p.x + xOffset), + y: scale * (middleYx2 - p.y + yOffset), + isOnCurve: p.isOnCurve + }); + }); + + var startPts = 0; // 起始点 + var currentPts = 0; // 结束点 + + var commandQueue = []; // 命令队列 + + // 处理glyf轮廓 + for ( var i = 0, l = glyf.endPtsOfContours.length; i < l; i++) { + try { + // 处理glyf坐标 + for ( var endPts = glyf.endPtsOfContours[i]; currentPts < endPts + 1; currentPts++) { + + var currentPoint = coordinates[currentPts]; + var prevPoint = (currentPts === startPts) + ? coordinates[endPts] + : coordinates[currentPts - 1]; + var nextPoint = (currentPts === endPts) + ? coordinates[startPts] + : coordinates[currentPts + 1]; + + if (currentPoint == undefined) { + continue; + } + + // 处理起始点 + if (currentPts === startPts) { + if (currentPoint.isOnCurve) { + commandQueue.push('M'); + commandQueue.push(currentPoint); + } + // 起始点不在曲线上 + else { + + var midPoint = { + x : (prevPoint.x + currentPoint.x) / 2, + y : (prevPoint.y + currentPoint.y) / 2 + }; + + commandQueue.push('M'); + commandQueue.push(midPoint); + + commandQueue.push('Q'); + commandQueue.push(currentPoint); + } + } + else { + + // 直线 + if ( + currentPoint != undefined + && currentPoint.isOnCurve + && prevPoint != undefined + && prevPoint.isOnCurve + ) { + commandQueue.push('L'); + } + // 当前点不在曲线上 + else if ( + !currentPoint.isOnCurve + && prevPoint != undefined + && !prevPoint.isOnCurve + ) { + + var midPoint = { + x : (prevPoint.x + currentPoint.x) / 2, + y : (prevPoint.y + currentPoint.y) / 2 + }; + commandQueue.push(midPoint); + } + // 当前坐标不在曲线上 + else if (!currentPoint.isOnCurve) { + commandQueue.push('Q'); + } + commandQueue.push(currentPoint); + } + } + + // 处理最后一个点 + if ( + !currentPoint.isOnCurve + && coordinates[startPts] != undefined + ) { + + // 轮廓起始点在曲线上 + if (coordinates[startPts].isOnCurve) { + commandQueue.push(coordinates[startPts]); + } + else { + var midPoint = { + x : (currentPoint.x + coordinates[startPts].x) / 2, + y : (currentPoint.y + coordinates[startPts].y) / 2 + }; + commandQueue.push(midPoint); + } + } + + // 结束轮廓 + commandQueue.push('Z'); + + // 处理下一个轮廓 + startPts = glyf.endPtsOfContours[i] + 1; + } catch (e) { + throw e; + } + } + + var offset = 0; + var command = 0; + var cur, start; + + commandQueue.unshift('Z'); + ctx.beginPath(); + while(command = commandQueue.shift()) { + + switch(command) { + case 'M': + cur = commandQueue.shift(); + ctx.moveTo(cur.x, cur.y); + break; + case 'L': + cur = commandQueue.shift(); + ctx.lineTo(cur.x, cur.y); + break; + // Q 可能有多个轮廓片段,2个为一组 + case 'Q': + var p; + cur = commandQueue.shift(); + while(typeof cur !== 'string') { + p = commandQueue.shift(); + ctx.quadraticCurveTo(cur.x, cur.y, p.x, p.y); + cur = commandQueue.shift(); + } + commandQueue.unshift(cur); + break; + case 'Z': + // 处理闭合问题 + if(start) { + ctx.lineTo(start.x, start.y); + } + + start = commandQueue[1]; + + break; + } + } + + if(options.stroke) { + ctx.stroke(); + } + else { + ctx.fill(); + } + + } + + return glyf2canvas; + } +); diff --git a/src/ttf/glyf2svg.js b/src/ttf/glyf2svg.js index c8fda1f..0b1abbf 100644 --- a/src/ttf/glyf2svg.js +++ b/src/ttf/glyf2svg.js @@ -20,15 +20,30 @@ define( * @param {Object} glyf 解析后的glyf结构 * @return {string} svg文本 */ - function glyf2svg(glyf) { - - var pathArray = []; - var startPts = 0; // 起始点 - var currentPts = 0; // 结束点 - + function glyf2svg(glyf, options) { if(!glyf) { return null; } + var pathArray = []; + var startPts = 0; // 起始点 + var currentPts = 0; // 结束点 + + options = options || {}; + + // 对轮廓进行反向,以及坐标系调整,取整 + var xOffset = -glyf.xMin; + var yOffset = -glyf.yMin; + var middleYx2 = glyf.yMax + glyf.yMin; + var scale = options.scale || 1; + var coordinates = []; + + glyf.coordinates.forEach(function(p) { + coordinates.push({ + x: scale * (p.x + xOffset), + y: scale * (middleYx2 - p.y + yOffset), + isOnCurve: p.isOnCurve + }); + }); // 处理glyf轮廓 for ( var i = 0, l = glyf.endPtsOfContours.length; i < l; i++) { @@ -37,13 +52,13 @@ define( for ( var endPts = glyf.endPtsOfContours[i]; currentPts < endPts + 1; currentPts++) { var path = ""; - var currentPoint = glyf.coordinates[currentPts]; + var currentPoint = coordinates[currentPts]; var prevPoint = (currentPts === startPts) - ? glyf.coordinates[endPts] - : glyf.coordinates[currentPts - 1]; + ? coordinates[endPts] + : coordinates[currentPts - 1]; var nextPoint = (currentPts === endPts) - ? glyf.coordinates[startPts] - : glyf.coordinates[currentPts + 1]; + ? coordinates[startPts] + : coordinates[currentPts + 1]; if (currentPoint == undefined) { continue; @@ -118,22 +133,22 @@ define( // 当前点不在曲线上 if ( !currentPoint.isOnCurve - && glyf.coordinates[startPts] != undefined + && coordinates[startPts] != undefined ) { // 轮廓起始点在曲线上 - if (glyf.coordinates[startPts].isOnCurve) { + if (coordinates[startPts].isOnCurve) { pathArray.push( - glyf.coordinates[startPts].x + coordinates[startPts].x + "," - + glyf.coordinates[startPts].y + + coordinates[startPts].y + " " ); } else { var midPoint = { - x : (currentPoint.x + glyf.coordinates[startPts].x) / 2, - y : (currentPoint.y + glyf.coordinates[startPts].y) / 2 + x : (currentPoint.x + coordinates[startPts].x) / 2, + y : (currentPoint.y + coordinates[startPts].y) / 2 }; pathArray.push(midPoint.x + "," + midPoint.y + " "); }