diff --git a/css/common/common.less b/css/common/common.less index 5695b48..07a92b6 100644 --- a/css/common/common.less +++ b/css/common/common.less @@ -1,6 +1,8 @@ @import './bootstrap.less'; +@import './def.less'; + body, html { margin: 0; padding: 0; @@ -10,14 +12,14 @@ body, html { // scroll bar ::-webkit-scrollbar { - width: 14px; - height: 14px; + width: 12px; + height: 12px; } ::-webkit-scrollbar-track, ::-webkit-scrollbar-thumb { border-radius: 999px; - border: 5px solid transparent; + border: 1px solid transparent; } ::-webkit-scrollbar-track { @@ -70,7 +72,7 @@ body, html { .sidebar { position: fixed; width: 200px; - top: 50px; + top: @top-height; bottom: 0; background: #FCFCFC; padding: 4px; @@ -79,11 +81,15 @@ body, html { .main { margin-left: 200px; - padding: 4px; - padding-top: 50px; + padding: 4px 0 4px 10px; + padding-top: @top-height; height: 100%; } +.main.editing { + margin-left: 70%; +} + // loading 动画 .loading { position: fixed; @@ -105,6 +111,15 @@ body, html { } +.editor { + position: fixed; + width: 70%; + top: @top-height; + bottom: 0; + z-index: 1; + background: #FEFEFE; + border-right: 1px solid #DDD; +} .selection-range { position: absolute; diff --git a/css/common/def.less b/css/common/def.less new file mode 100644 index 0000000..74650bc --- /dev/null +++ b/css/common/def.less @@ -0,0 +1,5 @@ + + + +// 头部高度 +@top-height: 50px; \ No newline at end of file diff --git a/css/common/editor.less b/css/common/editor.less new file mode 100644 index 0000000..ecf9a2f --- /dev/null +++ b/css/common/editor.less @@ -0,0 +1,35 @@ + +// 编辑器样式集合 + +.glyf-editor { + + width: 100%; + height: 100%; + font-size: 12px; + -webkit-user-select: none; + -moz-user-select: none; + + .editor-contextmenu { + + width: 120px; + border: 1px solid #999; + background: #FEFEFE; + line-height: 24px; + padding: 0 4px; + box-shadow: 1px 1px 1px #CCC; + + div { + padding-left: 10px; + cursor: pointer; + border-bottom: 1px solid #CCC; + } + + div:hover { + background: #EEE; + } + + div:last-child { + border-bottom: none; + } + } +} \ No newline at end of file diff --git a/css/common/ico.less b/css/common/ico.less index 7e682f4..00948e8 100644 --- a/css/common/ico.less +++ b/css/common/ico.less @@ -3,7 +3,7 @@ // fontface @font-face { font-family: 'fonteditor'; - src: url('../../font/iconfont.ttf') format('truetype'); + src: url('../font/iconfont.ttf') format('truetype'); } .i-edit, diff --git a/css/main.less b/css/main.less new file mode 100644 index 0000000..f91fb4e --- /dev/null +++ b/css/main.less @@ -0,0 +1,6 @@ + + +@import './ttf.less'; + + +@import './common/editor.less'; \ No newline at end of file diff --git a/css/ttf.less b/css/ttf.less index da9c5d7..4c02554 100644 --- a/css/ttf.less +++ b/css/ttf.less @@ -35,9 +35,8 @@ .glyf-list { - padding-bottom: 1000000px; - margin-bottom: -1000000px; - overflow: hidden; + height: 100%; + overflow: auto; >.glyf-item { diff --git a/index.html b/index.html new file mode 100644 index 0000000..f3262d5 --- /dev/null +++ b/index.html @@ -0,0 +1,120 @@ + + + + + FontEditor字体编辑器 + + + + + + + + + +
+
+
+
+ +
+
+
+ +
+
正在加载...
+ + + + + + + + + +
+ + \ No newline at end of file diff --git a/src/editor/Editor.js b/src/editor/Editor.js index 0a5782a..c735052 100644 --- a/src/editor/Editor.js +++ b/src/editor/Editor.js @@ -143,6 +143,15 @@ define( return this; }; + /** + * 是否改变过 + * + * @return {boolean} + */ + Editor.prototype.isChanged = function() { + return !this.history.atFirst(); + }; + /** * 从剪切板中获取 * diff --git a/src/editor/controller/initFont.js b/src/editor/controller/initFont.js index 9f01add..00bafc1 100644 --- a/src/editor/controller/initFont.js +++ b/src/editor/controller/initFont.js @@ -25,23 +25,18 @@ define( var contours = font.contours; var originX = this.axis.x; var originY = this.axis.y; - var scale = this.render.camera.scale; + // 不需要在此保存contours delete font.contours; font.rightSideBearing = font.advanceWidth - font.xMax; this.font = font; - // 重置历史 - this.history.reset(); - this.history.add(lang.clone(contours)); - // 设置字形 var shapes = contours.map(function(contour) { - var shape = {}; - pathAdjust(contour, scale, -scale); - shape.points = pathAdjust(contour, 1, 1, originX, originY); - return shape; + return { + points: contour + }; }); var fontLayer = this.fontLayer; @@ -50,6 +45,18 @@ define( shapes.forEach(function(shape) { fontLayer.addShape('path', shape); }); + + // 重置历史 + this.history.reset(); + this.history.add(lang.clone(shapes)); + + // 设置缩放 + var scale = this.render.camera.scale; + shapes.forEach(function(shape) { + pathAdjust(shape.points, scale, -scale); + pathAdjust(shape.points, 1, 1, originX, originY); + }); + fontLayer.refresh(); // 设置参考线 @@ -67,7 +74,7 @@ define( * @return {this} */ function setShapes(shapes) { - var origin = this.render.getLayer('axis').shapes[0]; + var origin = this.axis; var scale = this.render.camera.scale; var fontLayer = this.fontLayer; @@ -124,14 +131,14 @@ define( font.name = font.name || ''; var shapes = this.getShapes(); - shapes.forEach(function(g) { + var contours = lang.clone(shapes.map(function(shape) { + return shape.points; + })).forEach(function(g) { pathCeil(g); }); // 设置边界 - var box = computeBoundingBox.computePathBox.apply(null, shapes.map(function(shape) { - return shape.points; - })); + var box = computeBoundingBox.computePathBox.apply(null, contours); font.xMin = box.x; font.yMin = box.y; @@ -141,7 +148,7 @@ define( font.advanceWidth = font.xMax + font.rightSideBearing; delete font.advanceWidth; - font.contours = shapes; + font.contours = contours; return font; } diff --git a/src/fonteditor/main.js b/src/fonteditor/main.js new file mode 100644 index 0000000..fa0ec65 --- /dev/null +++ b/src/fonteditor/main.js @@ -0,0 +1,117 @@ +/** + * @file main.js + * @author mengke01 + * @date + * @description + * ttf管理器 + */ + +define( + function(require) { + + var GLYFViewer = require('./widget/glyfviewer'); + var GLYFEditor = require('./widget/glyfeditor'); + var ProjectViewer = require('./widget/projectviewer'); + var TTFManager = require('./widget/ttfmanager'); + var program = require('./widget/program'); + var controller = require('./widget/controller/default'); + var actions = require('./widget/actions'); + + // 打开文件 + function onUpFile(e) { + var file = e.target.files[0]; + + if (program.data.action == 'open' && file.name.match(/(\.ttf|\.woff)$/i)) { + program.loader.load(file, { + type: file.name.slice(file.name.lastIndexOf('.') + 1).toLowerCase(), + success: function(imported) { + program.viewer.clearSelected(); + program.ttfManager.set(imported); + program.data.projectName = null; + } + }); + } + else if (program.data.action == 'import' && file.name.match(/(\.ttf|\.woff|\.svg)$/i)) { + if (program.ttfManager.get()) { + program.loader.load(file, { + type: file.name.slice(file.name.lastIndexOf('.') + 1).toLowerCase(), + success: function(imported) { + if (imported.glyf.length) { + program.ttfManager.merge(imported, {scale: true}); + } + } + }); + } + } + else { + alert('无法识别文件类型!'); + } + + e.target.value = ''; + } + + // 绑定组件 + function bindEvent() { + $('.navbar').delegate('[data-action]', 'click', function(e) { + var action = $(this).attr('data-action'); + if (actions[action]) { + program.data.action = action; + actions[action].call(this, e); + } + }); + + $('#export-btn').on('mouseup', actions['export-file']); + $('#export-btn-woff').on('mouseup', actions['export-file']); + $('#export-btn-svg').on('mouseup', actions['export-file']); + + document.getElementById('font-import').addEventListener('change', onUpFile); + } + + var entry = { + + /** + * 初始化 + */ + init: function () { + bindEvent(); + + // glyf查看器 + program.viewer = new GLYFViewer($('#glyf-list')); + + // 字体查看器 + program.editor = new GLYFEditor($('#glyf-editor')); + + // 项目管理 + program.project = require('./widget/project'); + program.projectViewer = new ProjectViewer($('#project-list')); + + // ttf管理 + program.ttfManager = new TTFManager(); + + // 导入导出器 + program.loader = require('./widget/loader'); + program.exporter = require('./widget/exporter'); + + // 预览器 + program.previewer = require('./widget/previewer'); + + controller.init(program); + + // 加载项目 + program.projectViewer.show(program.project.items()); + + + // for test + $.getJSON('./demo/js/baiduHealth.json', function(ttf) { + program.ttfManager.set(ttf); + program.viewer.focus(); + }); + + } + }; + + entry.init(); + + return entry; + } +); \ No newline at end of file diff --git a/src/fonteditor/widget/controller/default.js b/src/fonteditor/widget/controller/default.js index 5c3f823..a9d34f8 100644 --- a/src/fonteditor/widget/controller/default.js +++ b/src/fonteditor/widget/controller/default.js @@ -9,7 +9,7 @@ define( function(require) { - + var lang = require('common/lang'); var clipboard = require('../clipboard'); var string = require('common/string'); @@ -21,10 +21,22 @@ define( * @param {Object} program 项目组件 */ init: function(program) { + program.viewer.on('del', function(e) { if (e.list) { program.ttfManager.removeGlyf(e.list); } + }).on('edit', function(e) { + $('.main').addClass('editing'); + $('.editor').addClass('editing'); + var list = program.ttfManager.getGlyf(e.list); + if (list[0].compound) { + alert('暂不支持复合字形!'); + } + else { + program.editor.show(lang.clone(list[0])); + } + }).on('copy', function(e) { var list = program.ttfManager.getGlyf(e.list); clipboard.set(list, 'glyf'); @@ -32,37 +44,10 @@ define( var list = program.ttfManager.getGlyf(e.list); clipboard.set(list, 'glyf'); program.ttfManager.removeGlyf(e.list); - }).on('paste', function(e) { - var glyfList = clipboard.get('glyf'); - if (glyfList && glyfList.length) { - program.ttfManager.appendGlyf(glyfList, program.viewer.getSelected()); - } }).on('undo', function(e) { program.ttfManager.undo(); }).on('redo', function(e) { program.ttfManager.redo(); - }).on('save', function(e) { - // 保存项目 - if (program.ttfManager.get()) { - if (program.data.projectName) { - program.project.add(program.data.projectName, program.ttfManager.get()); - program.ttfManager.setState('new'); - program.loading.show('保存成功..', 400); - } - else { - var name = ''; - if ((name = window.prompt('请输入项目名称:'))) { - name = string.encodeHTML(name); - var list = program.project.add(name, program.ttfManager.get()); - program.projectViewer.show(list); - program.data.projectName = name; - program.ttfManager.setState('new'); - program.loading.show('保存成功..', 400); - } - } - } - - }); program.projectViewer.on('open', function(e) { @@ -87,6 +72,39 @@ define( program.viewer.focus(); }); + + program.on('save', function(e) { + // 保存项目 + if (program.ttfManager.get()) { + if (program.data.projectName) { + program.project.add(program.data.projectName, program.ttfManager.get()); + program.ttfManager.setState('new'); + program.loading.show('保存成功..', 400); + } + else { + var name = ''; + if ((name = window.prompt('请输入项目名称:'))) { + name = string.encodeHTML(name); + var list = program.project.add(name, program.ttfManager.get()); + program.projectViewer.show(list); + program.data.projectName = name; + program.ttfManager.setState('new'); + program.loading.show('保存成功..', 400); + } + } + } + }).on('paste', function(e) { + var glyfList = clipboard.get('glyf'); + if (glyfList && glyfList.length) { + program.ttfManager.appendGlyf(glyfList, program.viewer.getSelected()); + } + }); + + program.init({ + viewer: program.viewer.main.get(0), + editor: program.editor.main.get(0) + }); + window.onbeforeunload = function() { if (program.ttfManager.isChanged()) { return '是否放弃保存当前项目?'; diff --git a/src/fonteditor/widget/glyfeditor.js b/src/fonteditor/widget/glyfeditor.js new file mode 100644 index 0000000..f31e567 --- /dev/null +++ b/src/fonteditor/widget/glyfeditor.js @@ -0,0 +1,59 @@ +/** + * @file GLYFEditor.js + * @author mengke01 + * @date + * @description + * 编辑器组件 + */ + + +define( + function(require) { + + var editor = require('editor/main'); + + + /** + * font查看器 + * + * @constructor + * @param {HTMLElement} main 主元素 + * @param {Object} options 参数 + */ + function GLYFEditor(main, options) { + this.main = $(main); + this.options = options || {}; + } + + GLYFEditor.prototype.show = function(font) { + if (!this.editor) { + this.editor = editor.create(this.main.get(0)); + } + this.main.show(); + if (font) { + this.editor.setFont(font); + } + this.editor.focus(); + this.editing = true; + }; + + GLYFEditor.prototype.hide = function() { + this.editor.blur(); + this.main.hide(); + this.editing = false; + }; + + GLYFEditor.prototype.isEditing = function() { + return this.editing; + }; + + // 导出editor的函数 + ['focus', 'blur', 'setFont', 'getFont', 'setShapes', 'getShapes'].forEach(function(fn) { + GLYFEditor.prototype[fn] = function() { + return this.editor ? this.editor[fn].apply(this.editor, arguments) : undefined; + }; + }); + + return GLYFEditor; + } +); diff --git a/src/fonteditor/widget/glyfviewer.js b/src/fonteditor/widget/glyfviewer.js index 7830a67..ab66913 100644 --- a/src/fonteditor/widget/glyfviewer.js +++ b/src/fonteditor/widget/glyfviewer.js @@ -1,5 +1,5 @@ /** - * @file glyflist.js + * @file GLYFViewer.js * @author mengke01 * @date * @description @@ -15,6 +15,7 @@ define( var GLYF_ITEM_TPL = '' + '
' + + '' + '' + '' + '' @@ -27,8 +28,7 @@ define( var keyMap = { 46: 'del', 67: 'copy', - 88: 'cut', - 86: 'paste' + 88: 'cut' }; // 显示glyf @@ -120,62 +120,47 @@ define( function downlistener(e) { var me = this; - if (e.keyCode >= 112 && e.keyCode <= 119 && e.keyCode !== 116) { - e.preventDefault(); - e.stopPropagation(); - me.fire('function', { - keyCode: e.keyCode - }); - } - // 保存 - if (83 === e.keyCode && e.ctrlKey) { - e.preventDefault(); - e.stopPropagation(); - me.fire('save'); - } - // 处理其他事件,需要focus - else { - if (me.listening) { - // 阻止ctrl+A默认事件 - if (65 === e.keyCode && e.ctrlKey) { - e.preventDefault(); - e.stopPropagation(); - me.main.children().addClass('selected'); - } - // 撤销 - else if(e.keyCode == 90 && e.ctrlKey) { - e.stopPropagation(); - me.fire('undo'); - } - // 重做 - else if(e.keyCode == 89 && e.ctrlKey) { - e.stopPropagation(); - me.fire('redo'); - } - // 取消选中 - else if (27 === e.keyCode) { - e.stopPropagation(); + if (me.listening) { + // 阻止ctrl+A默认事件 + if (65 === e.keyCode && e.ctrlKey) { + e.preventDefault(); + e.stopPropagation(); + me.main.children().addClass('selected'); + } + // 撤销 + else if(e.keyCode == 90 && e.ctrlKey) { + e.stopPropagation(); + me.fire('undo'); + } + // 重做 + else if(e.keyCode == 89 && e.ctrlKey) { + e.stopPropagation(); + me.fire('redo'); + } + // 取消选中 + else if (27 === e.keyCode) { + e.stopPropagation(); + me.clearSelected(); + } + // 其他操作 + else if (keyMap[e.keyCode] && (e.keyCode == 46 || e.ctrlKey)) { + e.stopPropagation(); + var selected = me.getSelected(); + + // 取消选中的glyf + if (e.keyCode == 46 || e.keyCode == 88) { me.clearSelected(); } - // 其他操作 - else if (keyMap[e.keyCode] && (e.keyCode == 46 || e.ctrlKey)) { - e.stopPropagation(); - var selected = me.getSelected(); - // 取消选中的glyf - if (e.keyCode == 46 || e.keyCode == 88) { - me.clearSelected(); - } - - // 粘贴和有选择的操作需要发事件 - if (e.keyCode == 86 || selected.length) { - me.fire(keyMap[e.keyCode], { - list: selected - }); - } + // 粘贴和有选择的操作需要发事件 + if (selected.length) { + me.fire(keyMap[e.keyCode], { + list: selected + }); } } } + } /** @@ -185,7 +170,7 @@ define( * @param {HTMLElement} main 主元素 * @param {Object} options 参数 */ - function GlyfViewer(main, options) { + function GLYFViewer(main, options) { this.options = options || {}; this.main = $(main); @@ -196,11 +181,6 @@ define( var me = this; me.downlistener = lang.bind(downlistener, this); - $(document.body).on('click', function(e) { - var focused = me.main.get(0) === e.target || me.main.get(0).contains(e.target); - focused ? me.focus() : me.blur(); - }); - me.capture = new MouseCapture(me.main.get(0), { events: { dblclick: false, @@ -249,7 +229,7 @@ define( /** * 获取焦点 */ - GlyfViewer.prototype.focus = function() { + GLYFViewer.prototype.focus = function() { if (!this.listening) { this.listening = true; document.body.addEventListener('keydown', this.downlistener); @@ -259,10 +239,10 @@ define( /** * 失去焦点 */ - GlyfViewer.prototype.blur = function() { + GLYFViewer.prototype.blur = function() { if (this.listening) { this.listening = false; - document.body.addEventListener('keydown', this.downlistener); + document.body.removeEventListener('keydown', this.downlistener); } }; @@ -272,7 +252,7 @@ define( * @param {Object} ttf ttfObject * @param {Array?} selectedList 选中的列表 */ - GlyfViewer.prototype.show = function(ttf, selectedList) { + GLYFViewer.prototype.show = function(ttf, selectedList) { showGLYF.call(this, ttf, selectedList); }; @@ -281,7 +261,7 @@ define( * * @return {Array} 选中的indexList */ - GlyfViewer.prototype.getSelected = function() { + GLYFViewer.prototype.getSelected = function() { var selected = []; this.main.find('.selected').each(function(index, item) { selected.push(+item.getAttribute('data-index')); @@ -292,14 +272,14 @@ define( /** * 清除选中列表 */ - GlyfViewer.prototype.clearSelected = function() { + GLYFViewer.prototype.clearSelected = function() { this.main.children().removeClass('selected'); }; /** * 设置选中的列表 */ - GlyfViewer.prototype.setSelected = function(selectedList) { + GLYFViewer.prototype.setSelected = function(selectedList) { if (selectedList && 0 !== selectedList.length) { var selectedHash = {}; @@ -315,8 +295,8 @@ define( } }; - require('common/observable').mixin(GlyfViewer.prototype); + require('common/observable').mixin(GLYFViewer.prototype); - return GlyfViewer; + return GLYFViewer; } ); diff --git a/src/fonteditor/widget/program.js b/src/fonteditor/widget/program.js index 9a09dde..745bd56 100644 --- a/src/fonteditor/widget/program.js +++ b/src/fonteditor/widget/program.js @@ -10,8 +10,76 @@ define( function(require) { + var observable = require('common/observable'); + + + // 绑定click事件 + function bindClick(components) { + var me = this; + document.body.addEventListener('click', function(e) { + + // 监听查看器 + if (components.viewer === e.target || components.viewer.contains(e.target)) { + me.viewer.focus(); + } + else { + me.viewer.blur(); + } + + // 监听编辑器 + if (components.editor) { + if (components.editor === e.target || components.editor.contains(e.target)) { + me.editor.focus(); + } + else { + me.editor.blur(); + } + } + + }, false); + } + + /** + * 绑定键盘事件 + */ + function bindKey() { + var me = this; + + document.body.addEventListener('keydown', function(e) { + + // 功能键 + if (e.keyCode >= 112 && e.keyCode <= 119 && e.keyCode !== 116) { + e.preventDefault(); + e.stopPropagation(); + me.fire('function', { + keyCode: e.keyCode + }); + } + // 保存 + else if (83 === e.keyCode && e.ctrlKey) { + e.preventDefault(); + e.stopPropagation(); + me.fire('save'); + } + // 粘贴 + else if ((e.keyCode == 86 && e.ctrlKey)) { + e.preventDefault(); + e.stopPropagation(); + me.fire('paste'); + } + }); + } + var program = { + /** + * 初始化 + */ + init: function(components) { + bindClick.call(this, components); + bindKey.call(this, components); + }, + /** * 暂存对象 * @@ -30,6 +98,8 @@ define( loading: require('./loading') }; + observable.mixin(program); + return program; } ); diff --git a/src/render/Render.js b/src/render/Render.js index 695030f..72c732c 100644 --- a/src/render/Render.js +++ b/src/render/Render.js @@ -208,7 +208,7 @@ define( * */ Render.prototype.focus = function() { - this.capture.start(); + //this.capture.start(); this.keyCapture.start(); }; @@ -217,7 +217,7 @@ define( * */ Render.prototype.blur = function() { - this.capture.stop(); + //this.capture.stop(); this.keyCapture.stop(); };