add ttf viewer

This commit is contained in:
mkwiser 2014-10-06 15:12:09 +08:00
parent 76e1a51bfd
commit 917d134698
20 changed files with 615 additions and 203 deletions

View File

@ -78,10 +78,16 @@ html {
font-family: 'fonteditor';
src: url('../font/iconfont.ttf') format('truetype');
}
.project-list dd {
.project .project-title {
font-weight: bold;
}
.project .project-list {
line-height: 20px;
}
.project .project-list div {
padding-left: 10px;
}
.project-list dd:hover {
.project .project-list div:hover {
color: red;
text-decoration: underline;
cursor: pointer;

View File

@ -4,18 +4,28 @@
@import './util.less';
@import './ico.less';
.project-list {
dd {
padding-left: 10px;
.project {
.project-title {
font-weight: bold;
}
dd:hover {
color: red;
text-decoration: underline;
cursor: pointer;
.project-list {
line-height: 20px;
div {
padding-left: 10px;
}
div:hover {
color: red;
text-decoration: underline;
cursor: pointer;
}
}
}
.glyf-list {
>.glyf-item {

Binary file not shown.

Binary file not shown.

BIN
font/empty.ttf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
{"version":1,"numTables":12,"searchRenge":128,"entrySelector":3,"rengeShift":64,"head":{"version":1,"fontRevision":1,"checkSumAdjustment":369537602,"magickNumber":1594834165,"flags":11,"unitsPerEm":1024,"created":"2014-12-06T22:05:19.000Z","modified":"2014-12-06T22:20:03.000Z","xMin":34,"yMin":0,"xMax":306,"yMax":682,"macStyle":0,"lowestRecPPEM":8,"fontDirectionHint":2,"indexToLocFormat":0,"glyphDataFormat":0},"glyf":[{"contours":[[{"x":34,"y":0,"onCurve":true},{"x":34,"y":682,"onCurve":true},{"x":306,"y":682,"onCurve":true},{"x":306,"y":0,"onCurve":true}],[{"x":68,"y":34,"onCurve":true},{"x":272,"y":34,"onCurve":true},{"x":272,"y":648,"onCurve":true},{"x":68,"y":648,"onCurve":true}]],"xMin":34,"yMin":0,"xMax":306,"yMax":682,"advanceWidth":374,"leftSideBearing":34,"name":".notdef"}],"cmap":{},"name":{"fontFamily":"fonteditor","fontSubFamily":"Medium","uniqueSubFamily":"FontEditor 1.0 : fonteditor : 6-10-2014","fullName":"fonteditor","version":"Version 1.0 ; ttfautohint (v0.94) -l 8 -r 50 -G 200 -x 14 -w \"G\" -f -s","postScriptName":"fonteditor"},"hhea":{"version":1,"ascent":812,"descent":-212,"lineGap":92,"advanceWidthMax":374,"minLeftSideBearing":34,"minRightSideBearing":68,"xMaxExtent":306,"caretSlopeRise":1,"caretSlopeRun":0,"caretOffset":0,"reserved0":0,"reserved1":0,"reserved2":0,"reserved3":0,"metricDataFormat":0,"numOfLongHorMetrics":1},"post":{"italicAngle":0,"postoints":65411,"underlinePosition":50,"underlineThickness":0,"isFixedPitch":0,"minMemType42":0,"maxMemType42":0,"minMemType1":0,"maxMemType1":1,"format":2},"OS/2":{"version":4,"xAvgCharWidth":1031,"usWeightClass":400,"usWidthClass":5,"fsType":0,"ySubscriptXSize":665,"ySubscriptYSize":716,"ySubscriptXOffset":0,"ySubscriptYOffset":143,"ySuperscriptXSize":665,"ySuperscriptYSize":716,"ySuperscriptXOffset":0,"ySuperscriptYOffset":491,"yStrikeoutSize":51,"yStrikeoutPosition":265,"sFamilyClass":0,"bFamilyType":2,"bSerifStyle":0,"bWeight":6,"bProportion":3,"bContrast":0,"bStrokeVariation":0,"bArmStyle":0,"bLetterform":0,"bMidline":0,"bXHeight":0,"ulUnicodeRange1":1,"ulUnicodeRange2":268435456,"ulUnicodeRange3":0,"ulUnicodeRange4":0,"achVendID":"PfEd","fsSelection":192,"usFirstCharIndex":0,"usLastCharIndex":0,"sTypoAscender":812,"sTypoDescender":-212,"sTypoLineGap":92,"usWinAscent":812,"usWinDescent":212,"ulCodePageRange1":1,"ulCodePageRange2":0,"sxHeight":792,"sCapHeight":0,"usDefaultChar":0,"usBreakChar":32,"usMaxContext":1}}

View File

@ -19,21 +19,7 @@ define(
*/
data: {},
/**
* loading动画
*
* @type {Object}
*/
loading: {
show: function(text) {
$('#loading').html(text || '正在加载...').show();
},
hide: function() {
$('#loading').hide();
}
}
loading: require('./widget/loading')
};
return program;

View File

@ -9,132 +9,53 @@
define(
function(require) {
var GLYFViewer = require('../widget/glyfviewer');
var loader = require('../widget/loader');
var exporter = require('../widget/exporter');
var project = require('../widget/project');
var ProjectViewer = require('../widget/projectviewer');
var ttfmanager = require('../widget/ttfmanager');
var program = require('../program');
var TTFReader = require('ttf/ttfreader');
var glyf2svg = require('ttf/util/glyf2svg');
var svg2ttfobject = require('ttf/svg2ttfobject');
var string = require('common/string');
var pathAdjust = require('graphics/pathAdjust');
var TTFWriter = require('ttf/ttfwriter');
var ttf2woff = require('ttf/ttf2woff');
var woff2ttf = require('ttf/woff2ttf');
var ttf2base64 = require('ttf/ttf2base64');
var woff2base64 = require('ttf/woff2base64');
var woffOptions = {
inflate: require('inflate').inflate
};
var GLYF_ITEM_TPL = ''
+ '<div data-index="${index}" class="glyf-item ${modify}">'
+ '<svg class="glyf" viewbox="0 0 ${unitsPerEm} ${unitsPerEm}"><g transform="scale(1, -1) translate(0, -${descent}) scale(0.95, 0.95) "><path class="path" ${d}/></g></svg>'
+ '<div class="unicode" title="${unicode}">${unicode}</div><div class="name" title="${name}">${name}</div>'
+ '</div>';
var actions = {
new: function() {
if (program.data.ttf && !window.confirm('是否放弃保存当前项目?')) {
return;
}
newEmpty();
},
open: function() {
$('#font-import').click();
},
svg: function() {
import: function() {
$('#font-import').click();
},
export: function() {
//$(this).attr('href', '#');
},
save: function() {
saveProj();
}
};
// 显示glyf
function showGLYF(ttf) {
var unitsPerEm = ttf.head.unitsPerEm;
var descent = unitsPerEm + ttf.hhea.descent;
var glyfStr = '', d = '';
ttf.glyf.forEach(function(glyf, index) {
var g = {
index: index,
modify: glyf.modify,
unitsPerEm: unitsPerEm,
descent: descent,
unicode: (glyf.unicode || []).map(function(u) {
return '$' + u.toString(16).toUpperCase();
}).join(','),
name: glyf.name
};
if (d = glyf2svg(glyf, ttf)) {
g.d = 'd="'+ d +'"';
// 保存项目
function saveProj() {
if (program.data.ttf) {
var name = '';
if(name = window.prompt('请输入项目名称:')) {
var list = project.add(string.encodeHTML(name), program.data.ttf);
program.projectViewer.show(list);
}
glyfStr += string.format(GLYF_ITEM_TPL, g);
});
$('#glyf-list').get(0).innerHTML = glyfStr;
}
}
// 加载ttf
function loadSFNT(file, type) {
program.loading.show();
var fileReader = new FileReader();
fileReader.onload = function(e) {
var buffer = e.target.result;
if (type == 'woff') {
buffer = woff2ttf(buffer, woffOptions);
}
var ttfReader = new TTFReader();
program.data.ttf = ttfReader.read(buffer);
showGLYF(program.data.ttf);
ttfReader.dispose();
fileReader = null;
program.loading.hide();
}
fileReader.onerror = function(e) {
program.loading.hide();
alert('读取文件出错!');
fileReader = null;
};
fileReader.readAsArrayBuffer(file);
}
// 加载svg
function loadSVG(file) {
program.loading.show();
var fileReader = new FileReader();
fileReader.onload = function(e) {
var buffer = e.target.result;
var imported = svg2ttfobject(buffer);
var ttf = program.data.ttf;
var scale = 1;
// 对导入的轮廓进行缩放处理
if (imported.head.unitsPerEm && imported.head.unitsPerEm != ttf.head.unitsPerEm) {
scale = ttf.head.unitsPerEm / imported.head.unitsPerEm;
}
imported.glyf.forEach(function(g) {
if (g.contours && g.contours.length) {
if (scale !== 1) {
g.contours.forEach(function(contour) {
pathAdjust(contour, scale, scale);
});
}
g.modify = 'new';
ttf.glyf.push(g);
}
});
showGLYF(program.data.ttf);
fileReader = null;
program.loading.hide();
}
fileReader.onerror = function(e) {
program.loading.hide();
alert('读取文件出错!');
fileReader = null;
};
fileReader.readAsText(file);
// 新建空白
function newEmpty() {
$.getJSON('./src/fonteditor/data/empty.json', function(imported) {
program.data.ttf = imported;
program.viewer.show(imported);
})
}
// 打开文件
@ -143,41 +64,45 @@ define(
if (program.action == 'open' && file.name.match(/(\.ttf|\.woff)$/)) {
program.data.file = file.name;
loadSFNT(file, file.name.slice(file.name.lastIndexOf('.') + 1));
loader.load(file, {
type: file.name.slice(file.name.lastIndexOf('.') + 1),
success: function(imported) {
program.data.ttf = imported;
program.viewer.show(imported);
}
});
}
else if (program.action == 'svg' && file.name.match(/\.svg$/)) {
else if (program.action == 'import' && file.name.match(/(\.ttf|\.woff|\.svg)$/)) {
if (program.data.ttf) {
loadSVG(file);
}
else {
alert('没有要编辑的文件!');
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);
}
}
});
}
}
else {
alert('无法识别文件类型!');
}
e.target.value = '';
}
function exportTTF(e) {
function exportFile(e) {
var ttf = program.data.ttf;
if (ttf) {
var buffer = new TTFWriter().write(ttf);
var target = $(e.target);
target.attr('download', (ttf.name.fontFamily || 'export') + '.ttf');
target.attr('href', ttf2base64(buffer));
exporter.export(ttf, {
type: target.attr('data-type'),
target: target
});
}
}
function exportWOFF(e) {
var ttf = program.data.ttf;
if (ttf) {
var buffer = ttf2woff(new TTFWriter().write(ttf));
e.target.download = (ttf.name.fontFamily || 'export') + '.woff';
e.target.href = ttf2base64(buffer);
}
}
// 绑定组件
function bindEvent() {
$('.navbar').delegate('[data-action]', 'click', function(e) {
@ -188,8 +113,9 @@ define(
}
});
$('#export-btn').on('mouseup', exportTTF);
$('#export-btn-woff').on('mouseup', exportWOFF);
$('#export-btn').on('mouseup', exportFile);
$('#export-btn-woff').on('mouseup', exportFile);
$('#export-btn-svg').on('mouseup', exportFile);
document.getElementById('font-import').addEventListener('change', onUpFile);
}
@ -201,6 +127,22 @@ define(
*/
init: function () {
bindEvent();
program.viewer = new GLYFViewer($('#glyf-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('是否放弃保存当前项目?')) {
return;
}
program.data.ttf = imported;
program.viewer.show(imported);
}
});
program.projectViewer.show(project.items());
}
};

View File

@ -0,0 +1,77 @@
/**
* @file exporter.js
* @author mengke01
* @date
* @description
* 导出器
*/
define(
function(require) {
var TTFWriter = require('ttf/ttfwriter');
var ttf2woff = require('ttf/ttf2woff');
var ttf2svg = require('ttf/ttf2svg');
var ttf2base64 = require('ttf/ttf2base64');
var woff2base64 = require('ttf/woff2base64');
var svg2base64 = require('ttf/svg2base64');
/**
* 导出SFNT结构字体
*
* @param {Object} ttf ttf字体结构
* @param {Object} options 参数
* @param {Object} options.type 文件类型
* @param {Object} options.fileName 文件名
* @param {Function} options.success 成功回调
* @param {Function} options.error 失败回调
*
* @return {HTMLElement} 导出按钮
*/
function exportFile(ttf, options) {
if (ttf) {
var base64Str = '';
if (options.type == 'woff') {
var buffer = ttf2woff(new TTFWriter().write(ttf));
base64Str = woff2base64(buffer);
}
else if(options.type == 'svg') {
base64Str = svg2base64(ttf2svg(ttf));
}
else {
buffer = new TTFWriter().write(ttf);
base64Str = ttf2base64(buffer);
options.type == 'ttf';
}
var target = $(options.target);
target.attr('download', (options.fileName || ttf.name.fontFamily || 'export') + '.' + options.type);
target.attr('href', base64Str);
options.success && options.success(base64Str);
}
return options.target;
}
var exporter = {
/**
* 导出SFNT结构字体
*
* @param {Object} ttf ttf字体结构
* @param {Object} options 参数
* @param {Object} options.type 文件类型
* @param {Object} options.fileName 文件名
* @param {Function} options.success 成功回调
* @param {Function} options.error 失败回调
*
* @return {HTMLElement} 导出按钮
*/
export: exportFile
};
return exporter;
}
);

View File

@ -0,0 +1,69 @@
/**
* @file glyflist.js
* @author mengke01
* @date
* @description
* glyf 查看器
*/
define(
function(require) {
var glyf2svg = require('ttf/util/glyf2svg');
var string = require('common/string');
var GLYF_ITEM_TPL = ''
+ '<div data-index="${index}" class="glyf-item ${compound} ${modify}">'
+ '<svg class="glyf" viewbox="0 0 ${unitsPerEm} ${unitsPerEm}"><g transform="scale(1, -1) translate(0, -${descent}) scale(0.95, 0.95) "><path class="path" ${d}/></g></svg>'
+ '<div class="unicode" title="${unicode}">${unicode}</div><div class="name" title="${name}">${name}</div>'
+ '</div>';
// 显示glyf
function showGLYF(ttf) {
var unitsPerEm = ttf.head.unitsPerEm;
var descent = unitsPerEm + ttf.hhea.descent;
var glyfStr = '', d = '';
ttf.glyf.forEach(function(glyf, index) {
var g = {
index: index,
compound: glyf.compound ? 'compound' : '',
modify: glyf.modify,
unitsPerEm: unitsPerEm,
descent: descent,
unicode: (glyf.unicode || []).map(function(u) {
return '$' + u.toString(16).toUpperCase();
}).join(','),
name: glyf.name
};
if (d = glyf2svg(glyf, ttf)) {
g.d = 'd="'+ d +'"';
}
glyfStr += string.format(GLYF_ITEM_TPL, g);
});
this.main.html(glyfStr);
}
/**
* glyf查看器
*
* @constructor
* @param {HTMLElement} main 主元素
* @param {Object} options 参数
*/
function GlyfViewer(main, options) {
this.options = options || {};
this.main = $(main);
}
GlyfViewer.prototype.show = function(ttf) {
showGLYF.call(this, ttf);
};
require('common/observable').mixin(GlyfViewer.prototype);
return GlyfViewer;
}
);

View File

@ -0,0 +1,119 @@
/**
* @file loader.js
* @author mengke01
* @date
* @description
* 加载器
*/
define(
function(require) {
var TTFReader = require('ttf/ttfreader');
var woff2ttf = require('ttf/woff2ttf');
var svg2ttfobject = require('ttf/svg2ttfobject');
var loading = require('./loading');
var woffOptions = {
inflate: require('inflate').inflate
};
/**
* 加载sfnt结构字体
*
* @param {File} file file对象
* @param {Object} options 参数
* @param {Object} options.type 文件类型
* @param {Function} options.success 成功回调
* @param {Function} options.error 失败回调
*/
function loadSFNT(file, options) {
loading.show();
var fileReader = new FileReader();
fileReader.onload = function(e) {
var buffer = e.target.result;
if (options.type == 'woff') {
buffer = woff2ttf(buffer, woffOptions);
}
var ttfReader = new TTFReader();
var ttf = ttfReader.read(buffer);
ttfReader.dispose();
fileReader = null;
loading.hide();
options.success && options.success(ttf);
}
fileReader.onerror = function(e) {
loading.hide();
fileReader = null;
alert('读取文件出错!');
options.error && options.error(e);
};
fileReader.readAsArrayBuffer(file);
}
/**
* 加载svg结构字体
*
* @param {File} file file对象
* @param {Object} options 参数
* @param {Function} options.success 成功回调
* @param {Function} options.error 失败回调
*/
function loadSVG(file, options) {
loading.show();
var fileReader = new FileReader();
fileReader.onload = function(e) {
var buffer = e.target.result;
var imported = svg2ttfobject(buffer);
fileReader = null;
loading.hide();
options.success && options.success(imported);
};
fileReader.onerror = function(e) {
loading.hide();
fileReader = null;
alert('读取文件出错!');
options.error && options.error(e);
};
fileReader.readAsText(file);
}
var loader = {
/**
* 加载字体
*
* @param {File} file file对象
* @param {Object} options 参数
* @param {Object} options.type 文件类型
* @param {Function} options.success 成功回调
* @param {Function} options.error 失败回调
*/
load: function(file, options) {
if (options.type == 'svg') {
loadSVG(file, options);
}
else if (options.type == 'ttf' || options.type == 'woff'){
loadSFNT(file, options);
}
else {
options.error && options.error({
message: '不支持的文件类型'
});
}
}
};
return loader;
}
);

View File

@ -0,0 +1,31 @@
/**
* @file loading.js
* @author mengke01
* @date
* @description
* loading 对象
*/
define(
function(require) {
/**
* 提示
*
* @type {Object}
*/
var loading = {
show: function(text) {
$('#loading').html(text || '正在加载...').show();
},
hide: function() {
$('#loading').hide();
}
};
return loading;
}
);

View File

@ -0,0 +1,97 @@
/**
* @file project.js
* @author mengke01
* @date
* @description
* 项目管理方法使用localStorage存储
*/
define(
function(require) {
var storage = window.localStorage || window.sessionStorate;
var project = {
/**
* 获取现有项目列表
*
* @return {Array} 现有项目列表
*/
items: function() {
var list = storage.getItem('project-list');
return list ? JSON.parse(list) : [];
},
/**
* 添加一个项目
*
* @param {string} projectName 项目名称
* @param {Object} ttf ttfObject
* @return {Array} 现有项目列表
*/
add: function(projectName, ttf) {
var list = this.items();
var id = Date.now();
list.push({
name: projectName,
id: id
});
storage.setItem('project-list', JSON.stringify(list));
storage.setItem(id, JSON.stringify(ttf));
return list;
},
/**
* 删除一个项目
*
* @param {string} projectName 项目名称
* @return {Array} 现有项目列表
*/
remove: function(projectName) {
var list = this.items();
for(var i = list.length - 1; i >= 0; i--) {
if (list[i].name == projectName) {
storage.removeItem(list[i].id);
list.splice(i, 1);
}
}
storage.setItem('project-list', JSON.stringify(list));
},
/**
* 获取一个项目
*
* @param {string} projectName 项目名称
* @return {Object=} 项目对象
*/
get: function(projectName) {
var list = this.items();
for(var i = 0, l = list.length; i < l ; i++) {
if (list[i].name == projectName) {
var item = storage.getItem(list[i].id);
if (item) {
return JSON.parse(item);
}
}
}
return null;
},
/**
* 清空项目组
*/
clear: function() {
var list = this.items();
list.forEach(function(item) {
storage.removeItem(item.id);
});
storage.removeItem('project-list');
}
};
return project;
}
);

View File

@ -0,0 +1,45 @@
/**
* @file projectViewer.js
* @author mengke01
* @date
* @description
* 项目浏览器
*/
define(
function(require) {
/**
* 项目查看器
*
* @constructor
* @param {HTMLElement} main 主元素
* @param {Object} options 参数
*/
function ProjectViewer(main, options) {
this.options = options || {};
this.main = $(main);
var me = this;
this.main.delegate('[data-name]', 'click', function(e) {
me.fire('open', {
projectName: $(this).attr('data-name')
});
});
}
ProjectViewer.prototype.show = function(projects) {
var str = '';
(projects || []).forEach(function(proj) {
str += '<div data-name="'+ proj.name +'" data-id="'+ proj.id +'"><a href="javascript:;">'+ proj.name +'</a></div>';
});
this.main.html(str);
};
require('common/observable').mixin(ProjectViewer.prototype);
return ProjectViewer;
}
);

View File

@ -0,0 +1,58 @@
/**
* @file ttf.js
* @author mengke01
* @date
* @description
* ttf相关操作类
*/
define(
function(require) {
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;
}
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;
}
};
return manager;
}
);

View File

@ -1,27 +0,0 @@
/**
* @file main.js
* @author mengke01
* @date
* @description
* 主函数入口
*/
define(
function(require) {
var entry = {
/**
* 初始化
*/
init: function () {
}
};
entry.init();
return entry;
}
);

View File

@ -174,7 +174,7 @@ define(
else if (cmd === 'L') {
// 这里可能会连续绘制,最后一个是终点
var q = 0, ql = segment.args.length - 2, px = 0, py = 0;
var q = 0, ql = segment.args.length, px = 0, py = 0;
if (relative) {
px = prevX;
@ -189,6 +189,8 @@ define(
});
}
ql = segment.args.length - 2;
if (relative) {
prevX += segment.args[ql];
prevY += segment.args[ql + 1];
@ -197,12 +199,6 @@ define(
prevX = segment.args[ql];
prevY = segment.args[ql + 1];
}
contour.push({
x: prevX,
y: prevY,
onCurve: true
});
}
// 二次贝塞尔
else if (cmd === 'Q') {

View File

@ -8,26 +8,28 @@
</head>
<body>
<div class="navbar navbar-inverse" role="navigation">
<section class="navbar navbar-inverse" role="navigation">
<button data-action="new" type="button" class="btn btn-primary btn-sm">新建</button>
<button data-action="open" type="button" class="btn btn-primary btn-sm">打开</button>
<button data-action="svg" type="button" class="btn btn-primary btn-sm">导入svg</button>
<a id="export-btn" href="#" data-action="export" class="btn btn-primary btn-sm">导出ttf</a>
<a id="export-btn-woff" href="#" data-action="export" class="btn btn-primary btn-sm">导出woff</a>
<button data-action="import" type="button" class="btn btn-primary btn-sm">导入</button>
<a id="export-btn" href="javascript:void(0)" data-action="export" data-type="ttf" class="btn btn-primary btn-sm">导出ttf</a>
<a id="export-btn-woff" href="javascript:void(0)" data-action="export" data-type="woff" class="btn btn-primary btn-sm">导出woff</a>
<a id="export-btn-svg" href="javascript:void(0)" data-action="export" data-type="svg" class="btn btn-primary btn-sm">导出svg</a>
<button data-action="save" type="button" class="btn btn-primary btn-sm">保存</button>
<button data-action="setting" type="button" class="btn btn-primary btn-sm">设置</button>
</div>
</section>
<div class="sidebar">
<dl class="project-list">
<dt>项目列表</dt>
<dd>baiduhealth.ttf</dd>
<dd>baiduhealth1.ttf</dd>
</dl>
</div>
<div class="main">
<section class="sidebar">
<div class="project">
<div class="project-title">项目列表</div>
<div id="project-list" class="project-list"></div>
</div>
</section>
<section class="main">
<div id="glyf-list" class="glyf-list">
</div>
</div>
</section>
<div id="loading" class="loading">正在加载...</div>