From 168984c7776a5142dc33280724e16118f2fbb8a7 Mon Sep 17 00:00:00 2001 From: mkwiser Date: Tue, 7 Oct 2014 02:34:38 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0ttf=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- css/bootstrap.less | 44 +++++ css/common.css | 43 ++++- css/common.less | 15 +- css/ico.css | 20 +++ css/ico.less | 22 +++ css/ttf.css | 105 ++++++++++- css/ttf.less | 55 +++++- font/iconfont.ttf | Bin 1328 -> 31236 bytes src/fonteditor/dialog/setting-unicode.js | 45 +++++ src/fonteditor/dialog/setting.js | 104 +++++++++++ src/fonteditor/ttf/main.js | 96 +++++++--- src/fonteditor/widget/glyfviewer.js | 147 ++++++++++++++- src/fonteditor/widget/project.js | 21 ++- src/fonteditor/widget/projectviewer.js | 15 +- src/fonteditor/widget/ttfmanager.js | 216 +++++++++++++++++++---- src/graphics/isBoundingBoxCross.js | 2 +- src/render/capture/Mouse.js | 26 ++- ttf.html | 59 ++++++- 18 files changed, 939 insertions(+), 96 deletions(-) create mode 100644 css/bootstrap.less create mode 100644 src/fonteditor/dialog/setting-unicode.js create mode 100644 src/fonteditor/dialog/setting.js diff --git a/css/bootstrap.less b/css/bootstrap.less new file mode 100644 index 0000000..1811d91 --- /dev/null +++ b/css/bootstrap.less @@ -0,0 +1,44 @@ + +// overwrite bootstrap themes + + +.modal-dialog { + margin-top: 100px; +} + +.modal-header { + padding-top: 6px; + padding-bottom: 6px; + background: #327EC0; + color: #FFF; + .close { + margin-top: 3px; + } + + .modal-title { + font-size: 14px; + line-height: 25px; + } +} + +.modal-footer { + padding-top: 10px; + padding-bottom: 10px; +} + +.modal-body { + min-height: 100px; +} + +.modal-content { + border-color: rgba(0,0,0,.5); + -webkit-box-shadow: 0 2px 4px rgba(0,0,0,.2); + box-shadow: 0 2px 4px rgba(0,0,0,.2); + overflow: hidden; +} + +.dropdown-menu { + a { + cursor: pointer; + } +} \ No newline at end of file diff --git a/css/common.css b/css/common.css index 03bb674..5cc9f70 100644 --- a/css/common.css +++ b/css/common.css @@ -1,3 +1,35 @@ +.modal-dialog { + margin-top: 100px; +} +.modal-header { + padding-top: 6px; + padding-bottom: 6px; + background: #327EC0; + color: #FFF; +} +.modal-header .close { + margin-top: 3px; +} +.modal-header .modal-title { + font-size: 14px; + line-height: 25px; +} +.modal-footer { + padding-top: 10px; + padding-bottom: 10px; +} +.modal-body { + min-height: 100px; +} +.modal-content { + border-color: rgba(0, 0, 0, 0.5); + -webkit-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + overflow: hidden; +} +.dropdown-menu a { + cursor: pointer; +} body, html { margin: 0; @@ -28,6 +60,7 @@ html { border-radius: 0; margin: 0; position: fixed; + z-index: 10; width: 100%; top: 0; } @@ -35,7 +68,7 @@ html { margin-right: 10px; margin-top: 10px; } -.navbar .btn:first-child { +.navbar > .btn:first-child { margin-left: 10px; } #export-btn { @@ -74,3 +107,11 @@ html { text-align: center; display: none; } +.selection-range { + position: absolute; + z-index: 6; + display: none; + border: 1px solid #CCC; + background: rgba(222, 222, 222, 0.5); + pointer-events: none; +} diff --git a/css/common.less b/css/common.less index 407b1e7..b9ea818 100644 --- a/css/common.less +++ b/css/common.less @@ -1,4 +1,6 @@ +@import './bootstrap.less'; + body, html { margin: 0; padding: 0; @@ -35,6 +37,7 @@ body, html { border-radius: 0; margin: 0; position: fixed; + z-index: 10; width: 100%; top: 0; @@ -43,7 +46,7 @@ body, html { margin-top: 10px; } - .btn:first-child { + >.btn:first-child { margin-left: 10px; } } @@ -88,4 +91,14 @@ body, html { line-height: 24px; text-align: center; display: none; +} + + +.selection-range { + position: absolute; + z-index: 6; + display: none; + border: 1px solid #CCC; + background: rgba(222, 222, 222, 0.5); + pointer-events: none; } \ No newline at end of file diff --git a/css/ico.css b/css/ico.css index 04229be..a7532cc 100644 --- a/css/ico.css +++ b/css/ico.css @@ -2,3 +2,23 @@ font-family: 'fonteditor'; src: url('../font/iconfont.ttf') format('truetype'); } +.i-edit, +.i-del { + display: inline-block; + font-family: 'fonteditor'; + font-size: 12px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -webkit-text-stroke-width: 0.1px; +} +.i-edit:hover, +.i-del:hover { + cursor: pointer; + color: blue; +} +.i-edit:before { + content: '\e605'; +} +.i-del:before { + content: '\e611'; +} diff --git a/css/ico.less b/css/ico.less index 3f51604..00948e8 100644 --- a/css/ico.less +++ b/css/ico.less @@ -6,3 +6,25 @@ src: url('../font/iconfont.ttf') format('truetype'); } +.i-edit, +.i-del { + display: inline-block; + font-family: 'fonteditor'; + font-size: 12px; + font-style:normal; + -webkit-font-smoothing: antialiased; + -webkit-text-stroke-width: 0.1px; + + &:hover { + cursor: pointer; + color: blue; + } +} + +.i-edit:before { + content: '\e605'; +} + +.i-del:before { + content: '\e611'; +} \ No newline at end of file diff --git a/css/ttf.css b/css/ttf.css index c8ce816..d18a35e 100644 --- a/css/ttf.css +++ b/css/ttf.css @@ -1,3 +1,35 @@ +.modal-dialog { + margin-top: 100px; +} +.modal-header { + padding-top: 6px; + padding-bottom: 6px; + background: #327EC0; + color: #FFF; +} +.modal-header .close { + margin-top: 3px; +} +.modal-header .modal-title { + font-size: 14px; + line-height: 25px; +} +.modal-footer { + padding-top: 10px; + padding-bottom: 10px; +} +.modal-body { + min-height: 100px; +} +.modal-content { + border-color: rgba(0, 0, 0, 0.5); + -webkit-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + overflow: hidden; +} +.dropdown-menu a { + cursor: pointer; +} body, html { margin: 0; @@ -28,6 +60,7 @@ html { border-radius: 0; margin: 0; position: fixed; + z-index: 10; width: 100%; top: 0; } @@ -35,7 +68,7 @@ html { margin-right: 10px; margin-top: 10px; } -.navbar .btn:first-child { +.navbar > .btn:first-child { margin-left: 10px; } #export-btn { @@ -74,26 +107,64 @@ html { text-align: center; display: none; } +.selection-range { + position: absolute; + z-index: 6; + display: none; + border: 1px solid #CCC; + background: rgba(222, 222, 222, 0.5); + pointer-events: none; +} @font-face { font-family: 'fonteditor'; src: url('../font/iconfont.ttf') format('truetype'); } +.i-edit, +.i-del { + display: inline-block; + font-family: 'fonteditor'; + font-size: 12px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -webkit-text-stroke-width: 0.1px; +} +.i-edit:hover, +.i-del:hover { + cursor: pointer; + color: blue; +} +.i-edit:before { + content: '\e605'; +} +.i-del:before { + content: '\e611'; +} .project .project-title { font-weight: bold; } .project .project-list { - line-height: 20px; + line-height: 24px; } .project .project-list div { - padding-left: 10px; + padding: 0 10px; } .project .project-list div:hover { - color: red; - text-decoration: underline; - cursor: pointer; + background: #EFEFEF; +} +.project .project-list div:hover .i-del { + display: block; +} +.project .project-list .i-del { + float: right; + display: none; +} +.glyf-list { + padding-bottom: 40px; + overflow: hidden; } .glyf-list > .glyf-item { float: left; + position: relative; margin: 10px; width: 86px; border: 1px solid #CCC; @@ -123,12 +194,34 @@ html { .glyf-list > .glyf-item .glyf .path { fill: green; } +.glyf-list > .glyf-item .i-del, +.glyf-list > .glyf-item .i-edit { + position: absolute; + right: 2px; + display: none; +} +.glyf-list > .glyf-item .i-edit { + right: 20px; +} .glyf-list > .glyf-item:hover { background: #EFEFEF; } .glyf-list > .glyf-item:hover .path { fill: darkgreen; } +.glyf-list > .glyf-item:hover .i-del, +.glyf-list > .glyf-item:hover .i-edit { + display: block; +} +.glyf-list > .compound .path { + fill: lightgreen!important; +} .glyf-list > .new .path { fill: blue!important; } +.glyf-list > .selected { + background: #EBFFC2!important; +} +.glyf-list.no-hover .glyf-item { + pointer-events: none; +} diff --git a/css/ttf.less b/css/ttf.less index e00836e..d86c2d8 100644 --- a/css/ttf.less +++ b/css/ttf.less @@ -10,15 +10,23 @@ font-weight: bold; } .project-list { - line-height: 20px; + line-height: 24px; div { - padding-left: 10px; + padding: 0 10px; } + div:hover { - color: red; - text-decoration: underline; - cursor: pointer; + background: #EFEFEF; + + .i-del { + display: block; + } + } + + .i-del { + float: right; + display: none; } } } @@ -27,9 +35,13 @@ .glyf-list { + padding-bottom: 40px; + overflow: hidden; + >.glyf-item { float: left; + position: relative; margin: 10px; width: 86px; border: 1px solid #CCC; @@ -59,6 +71,17 @@ fill: green; } } + + .i-del, + .i-edit { + position: absolute; + right: 2px; + display: none; + } + + .i-edit { + right: 20px; + } } >.glyf-item:hover { @@ -66,6 +89,17 @@ .path { fill: darkgreen; } + + .i-del, + .i-edit { + display: block; + } + } + + >.compound { + .path { + fill: lightgreen!important; + } } >.new { @@ -73,4 +107,15 @@ fill: blue!important; } } + + >.selected { + background: #EBFFC2!important; + } } + + +.glyf-list.no-hover { + .glyf-item { + pointer-events: none; + } +} \ No newline at end of file diff --git a/font/iconfont.ttf b/font/iconfont.ttf index 8d03301dde312b4d84bfeb3ec60b1fdcfe439490..e178ff5b2fecafffc8ff9123de02cf68b1cacc8c 100644 GIT binary patch literal 31236 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 delta 676 zcmYjOL2DCH5dP-9O&U!gTDLVw&{8Ud&}zGs9@K+}8oX4+s)$tT(xy#qwps1ARuCiT zMNm-R-Ge9bqKHR-fT)LpH$hSGAmYJ;f(K>&_GK$wcyH$WX1!zUo)k}V&f-0z)vu$l+tDDj1vzz-q1MEruMd){9T$!}wRfi^eaygK)E z*TP;vtrJ%ob$?d+zrLscmS?O%!ug1A)H=iijUZ~TsW<2+@t8!R*_!d;qQU~Nh;xCz z79xurW z$Z0E(v_!$YlHlo;cFILU?V(lCzP{%A5 zu!4ZyT3AAaBbJGXE~5`4*oR@-13YtSMn|)ekwnHY#LQvd9`6y1nr!oKUfKB&--O4N T_7sy%3U|ZJ#W9Na|CIj$j&Nq{ diff --git a/src/fonteditor/dialog/setting-unicode.js b/src/fonteditor/dialog/setting-unicode.js new file mode 100644 index 0000000..0c340d1 --- /dev/null +++ b/src/fonteditor/dialog/setting-unicode.js @@ -0,0 +1,45 @@ +/** + * @file setting-unicode.js + * @author mengke01 + * @date + * @description + * 设置代码点 + */ + +define( + function(require) { + + var tpl = '' + + '
' + + '
' + + '
' + + '' + + '' + + '
' + + '' + + '
' + + '
'; + + return require('./setting').derive({ + + getTpl: function() { + return tpl; + }, + onConfirm: function() { + var unicode = $('#setting-text-unicode').val(); + if (unicode.match(/^\$[A-E0-9]+$/i)) { + this.fire('change', { + unicode: unicode + }); + } + else { + alert('代码点设置不正确'); + return false; + } + } + }); + } +); diff --git a/src/fonteditor/dialog/setting.js b/src/fonteditor/dialog/setting.js new file mode 100644 index 0000000..5fc0359 --- /dev/null +++ b/src/fonteditor/dialog/setting.js @@ -0,0 +1,104 @@ +/** + * @file setting.js + * @author mengke01 + * @date + * @description + * 设置框 + */ + + +define( + function(require) { + var lang = require('common/lang'); + var observable = require('common/observable'); + + /** + * 设置框函数 + * + * @constructor + */ + function Setting() { + } + + /** + * 初始化绑定事件 + */ + Setting.prototype.preInit = function() { + var dlg = $('#model-dialog'); + dlg.find('.modal-title').html(this.title); + dlg.find('.modal-body').html(this.getTpl()); + + dlg.on('hidden.bs.modal', lang.bind(function (e) { + if (dlg) { + this.un(); + dlg.off('hidden.bs.modal'); + dlg.find('.btn-confirm').off('click'); + dlg = null; + } + }, this)); + + dlg.find('.btn-confirm').on('click', lang.bind(function() { + if (false !== this.onConfirm()) { + dlg.modal('hide'); + } + }, this)); + }; + + /** + * 获取模板 + * + * @return {string} 模板字符串 + */ + Setting.prototype.getTpl = function() { + return ''; + }; + + /** + * 确定事件 + * + * @return {boolean=} 是否关闭对话框 + */ + Setting.prototype.onConfirm = function() { + }; + + /** + * 显示 + */ + Setting.prototype.show = function() { + $('#model-dialog').modal('show'); + return this; + }; + + /** + * 注销 + */ + Setting.prototype.dispose = function() { + $('#model-dialog').modal('hide'); + }; + + + /** + * 派生一个setting + * + * @param {Object} proto 原型函数 + * @return {Function} 派生类 + */ + Setting.derive = function(proto) { + + function Class() { + Setting.apply(this, arguments); + this.preInit(); + this.initialize && this.initialize(); + } + + Class.prototype = new Setting(); + Class.prototype.constructor = Setting; + lang.extend(Class.prototype, proto); + observable.mixin(Class.prototype); + + return Class; + }; + + return Setting; + } +); diff --git a/src/fonteditor/ttf/main.js b/src/fonteditor/ttf/main.js index 9a7d9ae..c366af5 100644 --- a/src/fonteditor/ttf/main.js +++ b/src/fonteditor/ttf/main.js @@ -14,37 +14,72 @@ define( var exporter = require('../widget/exporter'); var project = require('../widget/project'); var ProjectViewer = require('../widget/projectviewer'); - var ttfmanager = require('../widget/ttfmanager'); + var TTFManager = require('../widget/ttfmanager'); var program = require('../program'); var string = require('common/string'); + var setting = { + 'unicode': require('../dialog/setting-unicode') + } + var actions = { - new: function() { - if (program.data.ttf && !window.confirm('是否放弃保存当前项目?')) { + 'new': function() { + if (program.ttfmanager.get() && !window.confirm('是否放弃保存当前项目?')) { return; } newEmpty(); - }, - open: function() { + }, + 'open': function() { $('#font-import').click(); }, - import: function() { + + 'import': function() { $('#font-import').click(); }, - export: function() { + + 'export': function() { }, - save: function() { + + 'save': function() { saveProj(); + }, + + 'add-new': function() { + program.ttfmanager.addglyf({ + name: '', + unicode:[] + }); + }, + + 'setting-unicode': function(e) { + var dlg = new setting.unicode(); + + dlg.on('change', function(e) { + // 此处延迟处理 + setTimeout(function(){ + setUnicode(e.unicode); + }, 20); + }); + + dlg.show(); } }; + // 设置unicode + function setUnicode(unicode) { + if (program.ttfmanager.get()) { + var glyfList = program.viewer.getSelected(); + program.ttfmanager.setUnicode(unicode, glyfList); + } + + } // 保存项目 function saveProj() { - if (program.data.ttf) { + if (program.ttfmanager.get()) { var name = ''; if(name = window.prompt('请输入项目名称:')) { - var list = project.add(string.encodeHTML(name), program.data.ttf); + var list = project.add(string.encodeHTML(name), program.ttfmanager.get()); program.projectViewer.show(list); } } @@ -53,8 +88,7 @@ define( // 新建空白 function newEmpty() { $.getJSON('./src/fonteditor/data/empty.json', function(imported) { - program.data.ttf = imported; - program.viewer.show(imported); + program.ttfmanager.set(imported); }) } @@ -63,23 +97,20 @@ define( var file = e.target.files[0]; if (program.action == 'open' && file.name.match(/(\.ttf|\.woff)$/)) { - program.data.file = file.name; loader.load(file, { type: file.name.slice(file.name.lastIndexOf('.') + 1), success: function(imported) { - program.data.ttf = imported; - program.viewer.show(imported); + program.ttfmanager.set(imported); } }); } else if (program.action == 'import' && file.name.match(/(\.ttf|\.woff|\.svg)$/)) { - if (program.data.ttf) { + if (program.ttfmanager.get()) { loader.load(file, { type: file.name.slice(file.name.lastIndexOf('.') + 1), success: function(imported) { if (imported.glyf.length) { - ttfmanager.combine(program.data.ttf, imported, {scale: true}); - program.viewer.show(program.data.ttf); + program.ttfmanager.combine(imported, {scale: true}); } } }); @@ -93,10 +124,9 @@ define( } function exportFile(e) { - var ttf = program.data.ttf; - if (ttf) { + if (program.ttfmanager.get()) { var target = $(e.target); - exporter.export(ttf, { + exporter.export(program.ttfmanager.get(), { type: target.attr('data-type'), target: target }); @@ -128,21 +158,39 @@ define( init: function () { bindEvent(); + // 查看器 program.viewer = new GLYFViewer($('#glyf-list')); + program.viewer.on('del', function(e) { + if (e.list) { + program.ttfmanager.delglyf(e.list); + } + }); + + // 项目管理 program.projectViewer = new ProjectViewer($('#project-list')); program.projectViewer.on('open', function(e) { var imported = project.get(e.projectName); if (imported) { - if (program.data.ttf && !window.confirm('是否放弃保存当前项目?')) { + if (program.ttfmanager.get() && !window.confirm('是否放弃保存当前项目?')) { return; } - program.data.ttf = imported; - program.viewer.show(imported); + program.ttfmanager.set(imported); + } + }); + program.projectViewer.on('del', function(e) { + if (e.projectName && window.confirm('是否删除项目?')) { + program.projectViewer.show(project.remove(e.projectName)); } }); program.projectViewer.show(project.items()); + // ttf管理 + program.ttfmanager = new TTFManager(); + program.ttfmanager.on('change', function(e) { + program.viewer.show(e.ttf); + }); + } }; diff --git a/src/fonteditor/widget/glyfviewer.js b/src/fonteditor/widget/glyfviewer.js index d2a1377..802c3e4 100644 --- a/src/fonteditor/widget/glyfviewer.js +++ b/src/fonteditor/widget/glyfviewer.js @@ -10,11 +10,15 @@ define( function(require) { var glyf2svg = require('ttf/util/glyf2svg'); var string = require('common/string'); - + var lang = require('common/lang'); + var MouseCapture = require('render/capture/Mouse'); + var isBoundingBoxCross = require('graphics/isBoundingBoxCross'); + var GLYF_ITEM_TPL = '' + '
' + + '' + '' - + '
${unicode}
${name}
' + + '
${unicode}
${name}
' + '
'; @@ -46,6 +50,28 @@ define( this.main.html(glyfStr); } + + // 点击item + function clickItem(e) { + $(this).toggleClass('selected'); + } + + // 点击item + function clickAction(e) { + e.stopPropagation(); + var target = $(e.target); + var action = target.attr('data-action'); + + if (action == 'del' && !window.confirm('确定删除字形么?')) { + return; + } + + var selected = [+target.parent().attr('data-index')]; + this.fire(action, { + list: selected + }); + } + /** * glyf查看器 * @@ -56,12 +82,129 @@ define( function GlyfViewer(main, options) { this.options = options || {}; this.main = $(main); + + this.main.delegate('[data-index]', 'click', clickItem) + .delegate('[data-action]', 'click', lang.bind(clickAction, this)); + + + var me = this; + // 绑定键盘事件 + me.listener = function(e) { + + // 删除 + if (46 === e.keyCode) { + e.stopPropagation(); + var selected = me.getSelected(); + if (selected.length) { + me.fire('del', { + list: selected + }); + } + } + // 取消选中 + else if (27 === e.keyCode) { + me.main.children().removeClass('selected'); + } + + }; + + + $(document.body).on('click', function(e) { + var focused = me.main.get(0) === e.target || me.main.get(0).contains(e.target); + if (focused && !me.listening) { + document.body.addEventListener('keyup', me.listener,false); + me.listening = true; + } + else if (!focused){ + document.body.removeEventListener('keyup', me.listener); + me.listening = false; + } + }); + + // 选择范围内元素 + function selectRangeItem(bound, toggle, remove) { + me.main.children().each(function(i, element) { + var item = $(element); + var pos = item.offset(); + var b = { + x: pos.left, + y: pos.top, + width: item.width(), + height: item.height() + } + if (3 === isBoundingBoxCross(bound, b)) { + if (toggle) { + item.toggleClass('selected'); + } + else if (remove) { + item.removeClass('selected') + } + else { + item.addClass('selected') + } + } + }); + } + + + me.capture = new MouseCapture(me.main.get(0), { + events: { + dblclick: false, + mousewheel: false, + mouseover: false, + mouseout: false + } + }); + + me.capture.on('dragstart', function(e) { + $('#selection-range').show(); + me.main.addClass('no-hover'); + me.startX = e.originEvent.pageX; + me.startY = e.originEvent.pageY; + }); + + var dragging = function(e) { + var x = e.originEvent.pageX; + var y = e.originEvent.pageY; + $('#selection-range').css({ + left: Math.min(me.startX, x), + top: Math.min(me.startY, y), + width: Math.abs(me.startX - x), + height: Math.abs(me.startY - y) + }); + }; + me.capture.on('drag', dragging); + + me.capture.on('dragend', function(e) { + $('#selection-range').hide(); + me.main.removeClass('no-hover'); + + var x = e.originEvent.pageX; + var y = e.originEvent.pageY; + var pos = me.main.offset(); + + selectRangeItem.call(me, { + x: Math.min(me.startX, x), + y: Math.min(me.startY, y), + width: Math.abs(me.startX - x), + height: Math.abs(me.startY - y) + }, e.ctrlKey, e.shiftKey); + }); + } GlyfViewer.prototype.show = function(ttf) { showGLYF.call(this, ttf); }; + GlyfViewer.prototype.getSelected = function() { + var selected = []; + this.main.find('.selected').each(function(index, item) { + selected.push(+item.getAttribute('data-index')); + }); + return selected; + }; + require('common/observable').mixin(GlyfViewer.prototype); return GlyfViewer; diff --git a/src/fonteditor/widget/project.js b/src/fonteditor/widget/project.js index e3f1038..f995204 100644 --- a/src/fonteditor/widget/project.js +++ b/src/fonteditor/widget/project.js @@ -33,11 +33,22 @@ define( */ add: function(projectName, ttf) { var list = this.items(); - var id = Date.now(); - list.push({ - name: projectName, - id: id + var exist = list.filter(function(l) { + return l.name == projectName; }); + + var id; + if (exist.length) { + id = exist[0].id; + } + else { + id = Date.now(); + list.push({ + name: projectName, + id: id + }); + } + storage.setItem('project-list', JSON.stringify(list)); storage.setItem(id, JSON.stringify(ttf)); return list; @@ -58,6 +69,8 @@ define( } } storage.setItem('project-list', JSON.stringify(list)); + + return list; }, /** diff --git a/src/fonteditor/widget/projectviewer.js b/src/fonteditor/widget/projectviewer.js index a6f1cc2..4cce994 100644 --- a/src/fonteditor/widget/projectviewer.js +++ b/src/fonteditor/widget/projectviewer.js @@ -22,7 +22,18 @@ define( this.main = $(main); var me = this; - this.main.delegate('[data-name]', 'click', function(e) { + + me.main.delegate('[data-action]', 'click', function(e) { + e.stopPropagation(); + var the = $(this); + me.fire(the.attr('data-action'), { + projectName: the.parent().attr('data-name') + }); + }); + + me.main.delegate('[data-name]', 'click', function(e) { + e.preventDefault(); + e.stopPropagation(); me.fire('open', { projectName: $(this).attr('data-name') }); @@ -32,7 +43,7 @@ define( ProjectViewer.prototype.show = function(projects) { var str = ''; (projects || []).forEach(function(proj) { - str += ''; + str += ''; }); this.main.html(str); diff --git a/src/fonteditor/widget/ttfmanager.js b/src/fonteditor/widget/ttfmanager.js index 278f7f1..5d84e80 100644 --- a/src/fonteditor/widget/ttfmanager.js +++ b/src/fonteditor/widget/ttfmanager.js @@ -10,49 +10,193 @@ define( function(require) { + var postName = require('ttf/enum/postName'); var pathAdjust = require('graphics/pathAdjust'); - var manager = { - /** - * 合并两个ttfObject,此处仅合并简单字形 - * - * @param {Object} ttf ttfObject - * @param {Object} imported ttfObject - * @param {Object} options 参数选项 - * @param {boolean} options.scale 是否自动缩放 - * - * @return {Object} 合并后的ttfObject - */ - combine: function(ttf, imported, options) { - options = options || {}; - // 调整glyf以适应打开的文件 - var scale = 1; - // 对导入的轮廓进行缩放处理 - if (options.scale && imported.head.unitsPerEm && imported.head.unitsPerEm != ttf.head.unitsPerEm) { - scale = ttf.head.unitsPerEm / imported.head.unitsPerEm; - } + /** + * 合并两个ttfObject,此处仅合并简单字形 + * + * @param {Object} ttf ttfObject + * @param {Object} imported ttfObject + * @param {Object} options 参数选项 + * @param {boolean} options.scale 是否自动缩放 + * + * @return {Object} 合并后的ttfObject + */ + function combine(ttf, imported, options) { + options = options || {}; - imported.glyf.filter(function(g, index) { - - return g.contours && g.contours.length //简单轮廓 - && g.name != '.notdef' && g.name != '.null' && g.name != 'nonmarkingreturn'; // 非预定义字形 - - }).forEach(function(g) { - if (scale !== 1) { - g.contours.forEach(function(contour) { - pathAdjust(contour, scale, scale); - }); - } - g.modify = 'new'; - ttf.glyf.push(g); - }); - - return ttf; + // 调整glyf以适应打开的文件 + var scale = 1; + // 对导入的轮廓进行缩放处理 + if (options.scale && imported.head.unitsPerEm && imported.head.unitsPerEm != ttf.head.unitsPerEm) { + scale = ttf.head.unitsPerEm / imported.head.unitsPerEm; } + + imported.glyf.filter(function(g, index) { + return g.contours && g.contours.length //简单轮廓 + && g.name != '.notdef' && g.name != '.null' && g.name != 'nonmarkingreturn'; // 非预定义字形 + + }).forEach(function(g) { + if (scale !== 1) { + g.contours.forEach(function(contour) { + pathAdjust(contour, scale, scale); + }); + } + g.modify = 'new'; + ttf.glyf.push(g); + }); + + return ttf; + } + + /** + * 构造函数 + * + * @constructor + * @param {ttfObject} ttf ttf对象 + */ + function Manager(ttf) { + this.ttf = ttf; + } + + /** + * 设置ttf + * + * @param {ttfObject} ttf ttf对象 + * @return {this} + */ + Manager.prototype.set = function(ttf) { + + if (this.ttf !== ttf) { + this.ttf = ttf; + this.fire('change', { + ttf: this.ttf + }); + } + + return this; }; - return manager; + /** + * 获取ttf对象 + * + * @return {ttfObject} ttf ttf对象 + */ + Manager.prototype.get = function() { + return this.ttf; + }; + + /** + * 添加glyf + * + * @param {Object} glyf glyf对象 + * + * @return {this} + */ + Manager.prototype.addglyf = function(glyf) { + this.ttf.glyf.push(glyf); + this.fire('change', { + ttf: this.ttf + }); + return this; + }; + + /** + * 合并两个ttfObject,此处仅合并简单字形 + * + * @param {Object} imported ttfObject + * @param {Object} options 参数选项 + * @param {boolean} options.scale 是否自动缩放 + * + * @return {this} + */ + Manager.prototype.combine = function(imported, options) { + combine(this.ttf, imported, options); + this.fire('change', { + ttf: this.ttf + }); + return this; + }; + + + /** + * 删除指定字形 + * + * @param {Array} indexList 索引列表 + * @return {this} + */ + Manager.prototype.delglyf = function(indexList) { + var glyf = this.ttf.glyf, count = 0; + for(var i = glyf.length - 1; i >= 0; i--) { + if (indexList.indexOf(i) >= 0) { + glyf.splice(i, 1); + count++; + } + } + + if (count) { + this.fire('change', { + ttf: this.ttf + }); + } + + return this; + }; + + + /** + * 设置unicode代码 + * + * @param {string} unicode unicode代码 + * @param {Array} indexList 索引列表 + * @return {this} + */ + Manager.prototype.setUnicode = function(unicode, indexList) { + var glyf = this.ttf.glyf, list; + if (indexList && indexList.length) { + list = indexList.map(function(item) { + return glyf[item]; + }); + } + else { + list = glyf; + } + + list = list.filter(function(g) { + return g.name != '.notdef' && g.name != '.null' && g.name != 'nonmarkingreturn'; + }); + + if (list.length) { + + unicode = Number('0x' + unicode.slice(1)); + + list.forEach(function(g) { + g.unicode = [unicode]; + g.name = unicode - 29 < 258 ? postName[unicode - 29] : 'uni' + unicode.toString(16).toUpperCase(); + unicode++; + }); + + this.fire('change', { + ttf: this.ttf + }); + } + + return this; + }; + + /** + * 注销 + */ + Manager.prototype.dispose = function() { + this.un(); + delete this.ttf; + }; + + require('common/observable').mixin(Manager.prototype); + + return Manager; } ); diff --git a/src/graphics/isBoundingBoxCross.js b/src/graphics/isBoundingBoxCross.js index 4915723..1f2c2f9 100644 --- a/src/graphics/isBoundingBoxCross.js +++ b/src/graphics/isBoundingBoxCross.js @@ -21,7 +21,7 @@ define( * @return {number} 包含关系 * * 2 : b2 包含 b1 - * 3 : b2 包含 b3 + * 3 : b1 包含 b2 * 1 : 有交点 */ function isBoundingBoxCross(b1, b2) { diff --git a/src/render/capture/Mouse.js b/src/render/capture/Mouse.js index 6e3db00..378244a 100644 --- a/src/render/capture/Mouse.js +++ b/src/render/capture/Mouse.js @@ -48,7 +48,8 @@ define( ctrlKey: e.ctrlKey, metaKey: e.metaKey, altKey: e.altKey, - shiftKey: e.shiftKey + shiftKey: e.shiftKey, + originEvent: e }; } @@ -73,12 +74,13 @@ define( */ function mousedown(e) { - prevent(e); if(false === this.events.mousedown) { return; } + prevent(e); + var event = getEvent(e); this.startX = event.x; @@ -117,12 +119,13 @@ define( * @param {Object} e 事件参数 */ function mousemove(e) { - prevent(e); if(false === this.events.mousemove) { return; } + prevent(e); + var event = getEvent(e); this.fire('move', event); @@ -155,12 +158,13 @@ define( */ function mouseup(e) { - prevent(e); if(false === this.events.mouseup) { return; } + prevent(e); + var event = getEvent(e); event.time = Date.now() - this.startTime; @@ -193,12 +197,12 @@ define( */ function mousewheel(e) { - prevent(e); - if(false === this.events.mousewheel) { return; } + prevent(e); + var delta = 0; if (e.wheelDelta) { delta = e.wheelDelta / 120; @@ -220,10 +224,13 @@ define( * @param {Object} e 事件参数 */ function mouseover(e) { - prevent(e); + if(false === this.events.mouseover) { return; } + + prevent(e); + this.fire('over'); } @@ -233,10 +240,13 @@ define( * @param {Object} e 事件参数 */ function mouseout(e) { - prevent(e); + if(false === this.events.mouseout) { return; } + + prevent(e); + this.fire('out'); } diff --git a/ttf.html b/ttf.html index 803f726..6f30d26 100644 --- a/ttf.html +++ b/ttf.html @@ -12,11 +12,39 @@ - 导出ttf - 导出woff - 导出svg - - + +
+ + + +
+ + 导出ttf + 导出woff + 导出svg + + + +
+ + + + +
+ +
正在加载...
+ + + + @@ -47,6 +94,6 @@ define('jquery', $); require(['fonteditor/ttf/main']) -
+
\ No newline at end of file