├── .babelrc ├── .fecsignore ├── .fecsrc ├── .gitignore ├── LICENSE ├── README.md ├── README.zh-CN.md ├── build.sh ├── build ├── i18n.en-us.js ├── i18n.zh-cn.js ├── index-loader.js └── tpl-loader.js ├── config ├── webpack.demo.js ├── webpack.dev.js └── webpack.prod.js ├── css ├── editor.less ├── img │ ├── logo@1x.png │ └── logo@2x.png ├── main.less ├── preview.lesstpl └── widget │ ├── bootstrap-overwrite.less │ ├── common.less │ ├── def.less │ ├── dialog.less │ ├── editor.less │ ├── glyf-download-dialog.less │ ├── glyf-editor.less │ ├── glyf-list.less │ ├── glyf-spliter.less │ ├── ico.less │ ├── import-pic-dialog.less │ ├── loading.less │ ├── page.less │ ├── project.less │ ├── reset.less │ └── util.less ├── demo ├── bezierSegmentCross.html ├── bezierSplit.html ├── boundSegmentCross.html ├── canvasFindBreakPoint.html ├── contoursCombine.html ├── css │ ├── editor.css │ ├── editor.less │ ├── editortest.css │ ├── editortest.less │ ├── render.less │ └── reset.less ├── data │ ├── baiduHealth.json │ ├── compound-glyf.js │ ├── contours-1.js │ ├── contours-10.js │ ├── contours-11.js │ ├── contours-12.js │ ├── contours-2.js │ ├── contours-3.js │ ├── contours-4.js │ ├── contours-5.js │ ├── contours-6.js │ ├── contours-7.js │ ├── contours-8.js │ ├── contours-9.js │ ├── contours.js │ ├── iconfont.json │ ├── image-contours-circle.js │ ├── image-contours0.js │ ├── image-contours1.js │ ├── image-contours10.js │ ├── image-contours11.js │ ├── image-contours12.js │ ├── image-contours13.js │ ├── image-contours14.js │ ├── image-contours15.js │ ├── image-contours2.js │ ├── image-contours3.js │ ├── image-contours4.js │ ├── image-contours5.js │ ├── image-contours6.js │ ├── image-contours7.js │ ├── image-contours8.js │ ├── image-contours9.js │ ├── shape-baidu.js │ └── shape-bdjk.js ├── datastore.html ├── editor-iframe.html ├── editor.html ├── editortest.html ├── findBreakPoints.html ├── fitContours.html ├── fitCurve.html ├── fitImage.html ├── index.html ├── js │ ├── bezierSegmentCross.js │ ├── bezierSplit.js │ ├── boundSegmentCross.js │ ├── canvasFindBreakPoint.js │ ├── contoursCombine.js │ ├── datastore.js │ ├── editor-iframe.js │ ├── editor.js │ ├── editortest.js │ ├── findBreakPoints.js │ ├── fitContours.js │ ├── fitCurve.js │ ├── fitImage.js │ ├── math.js │ ├── paper-boolean.js │ ├── procImage.js │ ├── quadraticBezier.js │ ├── quadraticBezierCross.js │ ├── reducePoints.js │ ├── render.js │ └── segmentCross.js ├── math.html ├── paper-boolean.html ├── procImage.html ├── quadraticBezierCross.html ├── reducePoints.html ├── render.html ├── segmentCross.html ├── sync │ ├── example-ascii.html │ ├── example.html │ ├── font.php │ ├── icon.css │ └── page.css └── test │ ├── a.gif │ ├── add.bmp │ ├── add.jpg │ ├── china.svg │ ├── circle.gif │ ├── circle.jpg │ ├── cl.gif │ ├── cur.jpg │ ├── ecomfe.png │ ├── oval.gif │ ├── qgqc.jpg │ ├── question.png │ ├── sjx.jpg │ ├── tiger.svg │ └── unicodenames.xlsx ├── dep ├── bootstrap │ ├── css │ │ └── bootstrap.min.css │ └── js │ │ └── bootstrap.min.js ├── favicon.ico ├── hidpi-canvas.js ├── jqColorPicker.min.js ├── jquery.min.js ├── jszip │ └── jszip.min.js ├── pako_deflate.min.js ├── pako_inflate.min.js ├── paper-full.js ├── utpl.min.js └── woff2 │ └── woff2.wasm ├── editor.tpl ├── empty.html ├── font ├── fonteditor.eot ├── fonteditor.svg ├── fonteditor.ttf └── fonteditor.woff ├── fonteditor-zh.jpg ├── fonteditor.jpg ├── index.tpl ├── package.json ├── php └── readOnline.php ├── proxy.html ├── src ├── common │ ├── DataStore.js │ ├── I18n.js │ ├── ajaxFile.js │ ├── getPixelRatio.js │ ├── lang.js │ ├── observable.js │ └── string.js ├── editor │ ├── Editor.js │ ├── command │ │ ├── align.js │ │ ├── commandSupport.js │ │ ├── editor.js │ │ ├── join.js │ │ ├── referenceline.js │ │ ├── setSelectedCommand.js │ │ ├── shape.js │ │ ├── support.js │ │ └── transform.js │ ├── controller │ │ ├── index.js │ │ ├── initAxis.js │ │ ├── initBinder.js │ │ ├── initFont.js │ │ ├── initLayer.js │ │ ├── initRender.js │ │ └── initSetting.js │ ├── group │ │ ├── BoundControl.js │ │ ├── ShapesGroup.js │ │ ├── getRotateMatrix.js │ │ ├── getScaleMatrix.js │ │ ├── moveTransform.js │ │ ├── rotateTransform.js │ │ └── scaleTransform.js │ ├── i18n │ │ ├── en-us.js │ │ ├── i18n.js │ │ └── zh-cn.js │ ├── main.js │ ├── menu │ │ ├── commandList.js │ │ ├── editor.js │ │ ├── point.js │ │ ├── shape.js │ │ └── shapes.js │ ├── mode │ │ ├── addpath.js │ │ ├── addshapes.js │ │ ├── bound.js │ │ ├── pan.js │ │ ├── point.js │ │ ├── range.js │ │ ├── referenceline.js │ │ ├── shapes.js │ │ ├── split.js │ │ └── support.js │ ├── options.js │ ├── shapes │ │ ├── arrow.js │ │ ├── circle.js │ │ ├── drop.js │ │ ├── du.js │ │ ├── heart.js │ │ ├── rect.js │ │ ├── roundrect.js │ │ ├── star.js │ │ ├── support.js │ │ ├── tel.js │ │ └── triangle.js │ ├── util │ │ ├── cursor.js │ │ └── getFontHash.js │ └── widget │ │ ├── ContextMenu.js │ │ ├── GraduationMarker.js │ │ ├── History.js │ │ ├── Sorption.js │ │ └── clipboard.js ├── fonteditor │ ├── config.js │ ├── controller │ │ ├── actions.js │ │ └── default.js │ ├── data │ │ └── online-font.js │ ├── dialog │ │ ├── colorpicker.js │ │ ├── font-online.js │ │ ├── font-url.js │ │ ├── glyf-download.js │ │ ├── setting-adjust-glyf.js │ │ ├── setting-adjust-pos.js │ │ ├── setting-editor.js │ │ ├── setting-find-glyf.js │ │ ├── setting-glyf.js │ │ ├── setting-ie.js │ │ ├── setting-import-pic.js │ │ ├── setting-metrics.js │ │ ├── setting-name.js │ │ ├── setting-sync.js │ │ ├── setting-unicode.js │ │ ├── setting.js │ │ └── support.js │ ├── editor.js │ ├── i18n │ │ ├── en-us │ │ │ ├── dialog.js │ │ │ ├── editor.js │ │ │ └── message.js │ │ ├── i18n.js │ │ └── zh-cn │ │ │ ├── dialog.js │ │ │ ├── editor.js │ │ │ └── message.js │ ├── index.js │ ├── menu │ │ ├── editor.js │ │ └── viewer.js │ ├── setting │ │ ├── editor.js │ │ ├── ie.js │ │ └── support.js │ ├── template │ │ ├── dialog │ │ │ ├── glyf-download.tpl │ │ │ ├── setting-adjust-glyf.tpl │ │ │ ├── setting-adjust-pos.tpl │ │ │ ├── setting-editor.tpl │ │ │ ├── setting-glyf.tpl │ │ │ ├── setting-ie.tpl │ │ │ ├── setting-import-pic.tpl │ │ │ ├── setting-metrics.tpl │ │ │ ├── setting-name.tpl │ │ │ └── setting-sync.tpl │ │ ├── export-render.js │ │ ├── export │ │ │ ├── icon-css.tpl │ │ │ ├── icon-example.tpl │ │ │ ├── preview-ttf.tpl │ │ │ └── symbol-example.tpl │ │ └── preview-render.js │ └── widget │ │ ├── DragSelector.js │ │ ├── GLYFEditor.js │ │ ├── Pager.js │ │ ├── ProjectViewer.js │ │ ├── Spliter.js │ │ ├── SyncForm.js │ │ ├── TTFManager.js │ │ ├── Toolbar.js │ │ ├── exporter.js │ │ ├── glyfviewer │ │ ├── GLYFViewer.js │ │ ├── binder.js │ │ └── render.js │ │ ├── loader.js │ │ ├── loading.js │ │ ├── previewer.js │ │ ├── program.js │ │ ├── project.js │ │ ├── settingmanager.js │ │ ├── sync-status.js │ │ ├── sync.js │ │ └── util │ │ ├── download.js │ │ ├── glyf2svgfile.js │ │ └── resolvettf.js ├── graphics │ ├── boundAdjust.js │ ├── computeBoundingBox.js │ ├── image │ │ ├── ContourPointsProcessor.js │ │ ├── ImageProcessor.js │ │ ├── contour │ │ │ ├── douglasPeuckerReducePoints.js │ │ │ ├── findBreakPoints.js │ │ │ ├── findContours.js │ │ │ ├── fitBezier.js │ │ │ ├── fitContour.js │ │ │ ├── fitCurve.js │ │ │ ├── fitOval.js │ │ │ ├── reducePoints.js │ │ │ └── smooth.js │ │ ├── filter │ │ │ ├── binarize.js │ │ │ ├── blur.js │ │ │ ├── brightness.js │ │ │ ├── close.js │ │ │ ├── dilate.js │ │ │ ├── erode.js │ │ │ ├── gaussBlur.js │ │ │ ├── gray.js │ │ │ ├── open.js │ │ │ ├── reverse.js │ │ │ └── sharp.js │ │ └── util │ │ │ ├── cloneContours.js │ │ │ ├── cloneImage.js │ │ │ ├── de.js │ │ │ ├── filteringImage.js │ │ │ ├── getHistogram.js │ │ │ ├── getThreshold.js │ │ │ └── threshold.js │ ├── isBezierCross.js │ ├── isBezierLineCross.js │ ├── isBezierRayCross.js │ ├── isBezierSegmentCross.js │ ├── isBoundingBoxCross.js │ ├── isBoundingBoxSegmentCross.js │ ├── isInsidePath.js │ ├── isInsidePolygon.js │ ├── isOnPath.js │ ├── isPathCross.js │ ├── isSegmentCross.js │ ├── isSegmentRayCross.js │ ├── join │ │ ├── getJoint.js │ │ └── getPathJoint.js │ ├── matrix.js │ ├── path │ │ └── circle.js │ ├── pathAdjust.js │ ├── pathBoolean.js │ ├── pathCeil.js │ ├── pathIterator.js │ ├── pathRotate.js │ ├── pathSkew.js │ ├── pathSplitBySegment.js │ ├── pathTransform.js │ ├── pathUtil.js │ ├── pathsUtil.js │ ├── reducePath.js │ ├── util.js │ └── vector.js ├── math │ ├── bezierCubeEquation.js │ ├── bezierCubic2Q2.js │ ├── bezierQ2Equation.js │ ├── bezierQ2Split.js │ ├── bezierQ4Equation.js │ ├── cubeEquation.js │ ├── getAngle.js │ ├── getBezierQ2Point.js │ ├── getBezierQ2T.js │ ├── quadraticEquation.js │ └── quarticEquation.js └── render │ ├── Camera.js │ ├── Controller.js │ ├── Layer.js │ ├── Painter.js │ ├── Render.js │ ├── capture │ ├── Keyboard.js │ ├── Mouse.js │ └── Resize.js │ ├── main.js │ ├── shape │ ├── Axis.js │ ├── BezierCurve.js │ ├── Circle.js │ ├── CirclePoint.js │ ├── Font.js │ ├── Glyf.js │ ├── Graduation.js │ ├── GridArrow.js │ ├── Line.js │ ├── Path.js │ ├── Point.js │ ├── Polygon.js │ ├── Rect.js │ ├── Shape.js │ ├── Text.js │ └── support.js │ └── util │ ├── dashedLineTo.js │ ├── drawAxis.js │ ├── drawGraduation.js │ ├── drawPath.js │ ├── guid.js │ └── selectShape.js └── test └── spec ├── graphics ├── isSegmentCross.spec.js └── vector.spec.js └── math └── getBezierQ2T.spec.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ], 5 | "plugins": [ 6 | [ 7 | "module-resolver", 8 | { 9 | "alias": { 10 | "graphics": "./src/graphics", 11 | "math": "./src/math", 12 | "fonteditor-core": "./node_modules/fonteditor-core/lib" 13 | } 14 | } 15 | ] 16 | ] 17 | } -------------------------------------------------------------------------------- /.fecsignore: -------------------------------------------------------------------------------- 1 | src/fonteditor/template/dialog/* 2 | src/fonteditor/template/export/* 3 | src/editor/shapes/* 4 | src/ttf/data/* 5 | build/* 6 | css/* 7 | demo/* 8 | dep/* 9 | font/* 10 | node_modules 11 | release 12 | test 13 | index.html 14 | index-en.html 15 | empty.html 16 | edp-build-config.js 17 | edp-build-config-node.js 18 | edp-webserver-config.js 19 | -------------------------------------------------------------------------------- /.fecsrc: -------------------------------------------------------------------------------- 1 | { 2 | "eslint": { 3 | "env": { 4 | "node": true, 5 | "browser": true 6 | }, 7 | 8 | "rules": { 9 | "no-console": 0, 10 | "fecs-max-statements": 0, 11 | "max-params": 0, 12 | "max-depth": 0, 13 | "fecs-camelcase": 0 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.seed 2 | *.log 3 | *.csv 4 | *.dat 5 | *.out 6 | *.pid 7 | *.gz 8 | *.tmp 9 | .DS_Store 10 | pids 11 | logs 12 | results 13 | release 14 | npm-debug.log 15 | node_modules 16 | css/common/*.css 17 | demo/sync/*.ttf 18 | demo/sync/*.woff 19 | demo/sync/*.eot 20 | demo/sync/*.svg 21 | 22 | dep/fonteditor-core 23 | 24 | dist -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 ecomfe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Fonteditor Online Font Editor 2 | ========== 3 | 4 | [English](./README.md) | [中文](./README.zh-CN.md) 5 | 6 | Use `Fonteditor` to edit, transform, preview fonts. 7 | 8 | Support ttf, woff, woff2, otf, svg font, eot import and edit online. 9 | 10 | [English Version](https://kekee000.github.io/fonteditor/index-en.html) 11 | 12 | 13 | ![Fonteditor](./fonteditor.jpg) 14 | 15 | ### Dev: 16 | 17 | ``` 18 | npm install && npm run dev 19 | ``` 20 | 21 | * Main entry template is `index.tpl`, using `index.tpl` to generate `index.html` and `index-en.html`. 22 | * Jszip 3.0 api has changed, currently use lower version of jszip. 23 | 24 | ### Build: 25 | 26 | ``` 27 | npm run build 28 | ``` 29 | 30 | ### Test: 31 | 32 | ``` 33 | npm run test 34 | ``` 35 | 36 | ### Demo: 37 | 38 | ``` 39 | npm run demo 40 | ``` 41 | 42 | ### Relative 43 | 44 | + Fonteditor Core Lib: [fonteditor-core](https://github.com/kekee000/fonteditor-core) 45 | + fontmin: [fontmin](https://github.com/ecomfe/fontmin) 46 | 47 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd $(dirname $0) 3 | [ -d ./dist ] && rm -r dist 4 | 5 | npm run prod 6 | 7 | cp -r dep dist 8 | cp -r font dist 9 | cp empty.html proxy.html dist 10 | echo 'build to `dist` done' -------------------------------------------------------------------------------- /build/i18n.en-us.js: -------------------------------------------------------------------------------- 1 | module.exports = exports = { 2 | lang: 'en-us', 3 | newglyph: 'New Glyph', 4 | undo: 'Undo', 5 | redo: 'Redo', 6 | import: 'Import', 7 | import_svg: 'Import svg', 8 | import_svg_title: 'Import svg font or svg file', 9 | import_pic: 'Import Image', 10 | import_pic_title: 'Import image with glyph', 11 | import_font: 'Import Font', 12 | import_font_title: 'Import ttf,woff,eot,otf font', 13 | export_ttf: 'Export ttf', 14 | export_woff: 'Export woff', 15 | export_woff2: 'Export woff2', 16 | export_zip: 'Export zip file, including ttf,woff,woff2,eot,svg font and examples.', 17 | save_proj: 'Save Project', 18 | onlinefont: 'Open Online Font', 19 | fonturl: 'Open Font from URL', 20 | tool: 'Tools', 21 | gen_glyph_name: 'Generate Glyph Name', 22 | clear_glyph_name: 'Clear Glyph Name', 23 | optimize_glyph: 'Optimize Glyph', 24 | sort_glyf: 'Sort Glyph by Unicode', 25 | compound2simple: 'Composite Glyph to Simple Glyph', 26 | preview: 'Preview', 27 | preview_ttf: 'ttf Font', 28 | preview_woff: 'woff Font', 29 | preview_woff2: 'woff2 Font', 30 | preview_svg: 'svg Font(Only safari)', 31 | preview_eot: 'eot Font(Only IE)', 32 | new_font: 'New', 33 | new_font_title: 'new font', 34 | open_font: 'Open', 35 | open_font_title: 'Open ttf,woff,eot,otf format font file', 36 | project_list: 'Project List', 37 | close_editor: 'Click or press `F2` to close glyph editor', 38 | metrics: 'Metrics', 39 | editor_setting: 'Editor Setting', 40 | import_and_export: 'Import and Export', 41 | setting: 'Setting', 42 | help: 'Help', 43 | confirm: 'Confirm', 44 | cancel: 'Cancel', 45 | fontinfo: 'Font Info', 46 | syncfromserver: 'Sync Font From Server' 47 | }; 48 | -------------------------------------------------------------------------------- /build/i18n.zh-cn.js: -------------------------------------------------------------------------------- 1 | module.exports = exports = { 2 | lang: 'zh-cn', 3 | newglyph: '新字形', 4 | undo: '撤销', 5 | redo: '重做', 6 | import: '导入', 7 | import_svg: '导入svg', 8 | import_svg_title: '导入svg格式字体文件', 9 | import_pic: '导入图片', 10 | import_pic_title: '导入包含字形的图片', 11 | import_font: '导入字体文件', 12 | import_font_title: '导入ttf,woff,eot,otf格式字体文件', 13 | export_ttf: '导出ttf', 14 | export_woff: '导出woff', 15 | export_woff2: '导出woff2', 16 | export_zip: '导出zip,包含ttf,woff,woff2,eot,svg等格式字体和icon示例', 17 | save_proj: '保存项目', 18 | onlinefont: '加载线上字体', 19 | fonturl: '从URL加载字体', 20 | tool: '工具', 21 | gen_glyph_name: '生成字形名称', 22 | clear_glyph_name: '清除字形名称', 23 | optimize_glyph: '优化字体', 24 | sort_glyf: '按代码点进行排序', 25 | compound2simple: '复合字形转简单字形', 26 | preview: '预览', 27 | preview_ttf: 'ttf字体', 28 | preview_woff: 'woff字体', 29 | preview_woff2: 'woff2字体', 30 | preview_svg: 'svg字体(仅safari)', 31 | preview_eot: 'eot字体(仅IE)', 32 | new_font: '新建', 33 | new_font_title: '新建ttf字体文件', 34 | open_font: '打开', 35 | open_font_title: '打开ttf,woff,eot,otf格式字体文件', 36 | project_list: '项目列表', 37 | close_editor: '点击或者按`F2`键关闭编辑器', 38 | metrics: '字体度量', 39 | editor_setting: '编辑器设置', 40 | import_and_export: '导入和导出', 41 | setting: '设置', 42 | help: '帮助', 43 | confirm: '确定', 44 | cancel: '取消', 45 | fontinfo: '字体信息', 46 | syncfromserver: '从服务器同步字体' 47 | }; 48 | -------------------------------------------------------------------------------- /build/index-loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 编译首页文件,编译为中文版和英文版 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | /** 7 | * 字符串格式化,支持如 ${xxx.xxx} 的语法 8 | * @param {string} source 模板字符串 9 | * @param {Object} data 数据 10 | * @return {string} 格式化后字符串 11 | */ 12 | function format(source, data) { 13 | return source.replace(/\$\{([\w.]+)\}/g, function ($0, $1) { 14 | let ref = $1.split('.'); 15 | let refObject = data; 16 | let level; 17 | 18 | while (refObject != null && (level = ref.shift())) { 19 | refObject = refObject[level]; 20 | } 21 | 22 | return refObject != null ? refObject : ''; 23 | }); 24 | } 25 | 26 | 27 | module.exports = function main(tpl) { 28 | let language = String(this.resource).match(/\?en-us/) ? 'en-us' : 'zh-cn'; 29 | let i18n = {}; 30 | i18n.lang = require('./i18n.' + language); 31 | let fileContent = format(tpl, i18n); 32 | return 'module.exports=' + JSON.stringify(fileContent); 33 | }; 34 | -------------------------------------------------------------------------------- /build/tpl-loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 编译首页文件,编译为中文版和英文版 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | module.exports = function main(tpl) { 7 | return 'module.exports=' + JSON.stringify(tpl); 8 | }; 9 | -------------------------------------------------------------------------------- /css/editor.less: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | } 5 | 6 | @import './widget/reset.less'; 7 | @import './widget/ico.less'; 8 | @import './widget/glyf-editor.less'; 9 | 10 | 11 | .editor-panel { 12 | width: 100%; 13 | height: 100%; 14 | font-size: 12px; 15 | -webkit-user-select: none; 16 | -moz-user-select: none; 17 | -ms-user-select: none; 18 | } 19 | -------------------------------------------------------------------------------- /css/img/logo@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fonteditor/dd7cc302bb3a742ca5821d9f3103a4fcdfdf6588/css/img/logo@1x.png -------------------------------------------------------------------------------- /css/img/logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fonteditor/dd7cc302bb3a742ca5821d9f3103a4fcdfdf6588/css/img/logo@2x.png -------------------------------------------------------------------------------- /css/main.less: -------------------------------------------------------------------------------- 1 | @import './widget/util.less'; 2 | @import './widget/common.less'; 3 | @import './widget/page.less'; 4 | 5 | @import './widget/loading.less'; 6 | @import './widget/project.less'; 7 | @import './widget/glyf-list.less'; 8 | @import './widget/glyf-spliter.less'; 9 | @import './widget/editor.less'; 10 | @import './widget/glyf-editor.less'; 11 | @import './widget/dialog.less'; 12 | @import './widget/import-pic-dialog.less'; 13 | @import './widget/glyf-download-dialog.less'; 14 | -------------------------------------------------------------------------------- /css/preview.lesstpl: -------------------------------------------------------------------------------- 1 | @import './widget/reset.less'; 2 | 3 | .main { 4 | padding:30px 100px; 5 | 6 | h1 { 7 | font-size:36px; 8 | color:#333; 9 | text-align:left; 10 | margin-bottom:30px; 11 | border-bottom:1px solid #eee 12 | } 13 | } 14 | 15 | .helps { 16 | margin-top:40px; 17 | 18 | pre { 19 | padding:20px; 20 | margin:10px 0; 21 | border:solid 1px #e7e1cd; 22 | background-color:#fffdef; 23 | overflow:auto 24 | } 25 | } 26 | 27 | .iconfont-list { 28 | overflow: hidden; 29 | 30 | li { 31 | float:left; 32 | width:100px; 33 | height:150px; 34 | text-align:center 35 | } 36 | 37 | .icon { 38 | font-size:42px; 39 | line-height:100px; 40 | margin:10px 0; 41 | color:#333; 42 | font-style: normal; 43 | -webkit-transition:font-size .25s ease-out 0s; 44 | -moz-transition:font-size .25s ease-out 0s; 45 | transition:font-size .25s ease-out 0s; 46 | } 47 | 48 | .icon:hover { 49 | font-size:100px 50 | } 51 | 52 | .code { 53 | color: green; 54 | font-weight: bold; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /css/widget/common.less: -------------------------------------------------------------------------------- 1 | // 页面背景样式定义 2 | @import './reset.less'; 3 | @import './bootstrap-overwrite.less'; 4 | @import './def.less'; 5 | @import './ico.less'; 6 | 7 | body, 8 | html { 9 | margin: 0; 10 | padding: 0; 11 | font-size: 13px; 12 | height: 100%; 13 | } 14 | 15 | // scroll bar 16 | ::-webkit-scrollbar { 17 | width: 12px; 18 | height: 12px; 19 | } 20 | 21 | ::-webkit-scrollbar-track, 22 | ::-webkit-scrollbar-thumb { 23 | border-radius: 999px; 24 | border: 1px solid transparent; 25 | } 26 | 27 | ::-webkit-scrollbar-track { 28 | box-shadow: 1px 1px 5px rgba(0,0,0,.2) inset; 29 | } 30 | 31 | ::-webkit-scrollbar-thumb { 32 | min-height: 20px; 33 | background-clip: content-box; 34 | box-shadow: 0 0 0 5px rgba(0,0,0,.2) inset; 35 | } 36 | 37 | ::-webkit-scrollbar-corner { 38 | background: transparent; 39 | } 40 | -------------------------------------------------------------------------------- /css/widget/def.less: -------------------------------------------------------------------------------- 1 | 2 | // 头部高度 3 | @top-height: 50px; 4 | 5 | // 菜单组高度 6 | @command-groups-height: 40px; -------------------------------------------------------------------------------- /css/widget/dialog.less: -------------------------------------------------------------------------------- 1 | // 对话框样式 2 | 3 | .modal { 4 | background: -webkit-radial-gradient(center, circle contain, rgba(255,255,255,0.6) 0%, rgba(0,0,0,0.6) 100%); 5 | } 6 | 7 | .modal-dialog { 8 | margin-top: 100px; 9 | 10 | hr { 11 | margin-top: 0; 12 | } 13 | } 14 | 15 | .modal-header { 16 | padding-top: 6px; 17 | padding-bottom: 6px; 18 | background: #fff; 19 | color: #202430; 20 | .close { 21 | margin-top: 3px; 22 | } 23 | 24 | .modal-title { 25 | font-size: 14px; 26 | line-height: 25px; 27 | } 28 | } 29 | 30 | 31 | 32 | .modal-body { 33 | min-height: 100px; 34 | padding-bottom: 5px; 35 | background: #F8F9F9; 36 | } 37 | 38 | .modal-content { 39 | border-radius: 0; 40 | border-color: rgba(0,0,0,.5); 41 | -webkit-box-shadow: 0 2px 4px rgba(0,0,0,.2); 42 | box-shadow: 0 2px 4px rgba(0,0,0,.2); 43 | overflow: hidden; 44 | } 45 | 46 | .modal-footer { 47 | border-top: 0; 48 | background: #F8F9F9; 49 | padding-top: 10px; 50 | padding-bottom: 10px; 51 | 52 | .btn-default { 53 | border-radius: 0; 54 | color: #FFF; 55 | background: #738089; 56 | } 57 | 58 | .btn-primary { 59 | border-radius: 0; 60 | background: #4A90E2; 61 | } 62 | } 63 | 64 | .setting-metrics { 65 | .modal-dialog { 66 | width: 800px; 67 | } 68 | .panose-inline { 69 | margin-bottom: -15px; 70 | .input-group { 71 | width: 100%; 72 | } 73 | } 74 | 75 | } 76 | 77 | 78 | .list-font-online { 79 | max-height: 400px; 80 | overflow: auto; 81 | } 82 | -------------------------------------------------------------------------------- /css/widget/glyf-download-dialog.less: -------------------------------------------------------------------------------- 1 | // 字形下载对话框 2 | .glyf-download-dialog { 3 | 4 | .field-margin { 5 | margin-left: 15px; 6 | } 7 | 8 | .color-picker { 9 | width: 25px; 10 | height: 25px; 11 | } 12 | 13 | .preview-panel { 14 | height: 360px; 15 | text-align: center; 16 | overflow: auto; 17 | position: relative; 18 | 19 | >canvas { 20 | position: absolute; 21 | top: 50%; 22 | left: 50%; 23 | transform: translate(-50%, -50%); 24 | } 25 | } 26 | 27 | .glyf-download-btn { 28 | text-align: center; 29 | 30 | .btn-flat { 31 | padding: 10px 15px; 32 | } 33 | 34 | .field-margin { 35 | margin-left: 30px; 36 | } 37 | } 38 | } 39 | 40 | // color picker 样式 41 | .cp-color-picker { 42 | z-index: 10000; 43 | } 44 | -------------------------------------------------------------------------------- /css/widget/glyf-spliter.less: -------------------------------------------------------------------------------- 1 | .main .spliter { 2 | position: absolute; 3 | height: 80%; 4 | width: 6px; 5 | } 6 | -------------------------------------------------------------------------------- /css/widget/import-pic-dialog.less: -------------------------------------------------------------------------------- 1 | // 导入图片对话框 2 | .import-pic-dialog { 3 | .modal-dialog { 4 | width: 960px; 5 | } 6 | 7 | .modal-body { 8 | padding-bottom: 0; 9 | } 10 | 11 | .preview-panel { 12 | background: #FFF; 13 | margin-bottom: 15px; 14 | 15 | .canvas-left, 16 | .canvas-right { 17 | display: inline-block; 18 | width: 50%; 19 | height: 420px; 20 | overflow: auto; 21 | } 22 | 23 | .canvas-left { 24 | border-right: 1px solid #BAC1CB; 25 | } 26 | 27 | &.fitpanel { 28 | canvas { 29 | max-width: 100%; 30 | max-height: 99%; 31 | } 32 | } 33 | 34 | &.showleft { 35 | .canvas-left { 36 | width: 100%; 37 | border-right: none; 38 | display: inline-block; 39 | } 40 | .canvas-right { 41 | display: none; 42 | } 43 | } 44 | 45 | &.showright { 46 | .canvas-right { 47 | width: 100%; 48 | display: inline-block; 49 | } 50 | .canvas-left { 51 | display: none; 52 | } 53 | } 54 | } 55 | 56 | .import-pic-url { 57 | position: absolute; 58 | margin-top: 30px; 59 | background: #4A90E2; 60 | padding: 10px 10px 0; 61 | border-bottom-left-radius: 5px; 62 | border-bottom-right-radius: 5px; 63 | display: none; 64 | 65 | &.show-url { 66 | display: block; 67 | } 68 | } 69 | } 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /css/widget/loading.less: -------------------------------------------------------------------------------- 1 | // loading 动画 2 | .loading { 3 | position: fixed; 4 | left: 50%; 5 | top: 30%; 6 | width: 200px; 7 | margin-left: -100px; 8 | line-height: 24px; 9 | text-align: center; 10 | z-index: 10000; 11 | display: none; 12 | 13 | span { 14 | display: inline-block; 15 | padding: 0 6px; 16 | background: rgba(84, 114, 93, 0.9); 17 | color: #FFF; 18 | border: 1px solid #DDD; 19 | } 20 | } 21 | 22 | .loading[data-status="error"] { 23 | span { 24 | color: red; 25 | background: rgba(236, 234, 69, 0.9); 26 | } 27 | } 28 | 29 | .loading[data-status="warn"] { 30 | span { 31 | color: #FF8722; 32 | background: rgba(255, 255, 255, 0.9); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /css/widget/project.less: -------------------------------------------------------------------------------- 1 | // 项目模块相关样式 2 | .project-btns { 3 | margin: 0 15px; 4 | padding: 10px 0; 5 | border-bottom: #353D45 1px solid; 6 | 7 | .btn { 8 | padding-left: 10px; 9 | padding-right: 12px; 10 | } 11 | 12 | .btn:last-child { 13 | margin-left: 10px; 14 | } 15 | } 16 | 17 | .project { 18 | .project-title { 19 | font-weight: bold; 20 | color: #6F7D88; 21 | padding-left: 15px; 22 | line-height: 32px; 23 | } 24 | 25 | .project-list { 26 | dl { 27 | padding: 0 10px 0 15px; 28 | cursor: pointer; 29 | 30 | dt { 31 | line-height: 28px; 32 | font-weight: normal; 33 | color: #9EB0C0; 34 | } 35 | 36 | dd { 37 | display: none; 38 | line-height: 28px; 39 | color: #6F7D88; 40 | span { 41 | margin-right: 10px; 42 | &:hover { 43 | text-decoration: underline; 44 | } 45 | } 46 | } 47 | } 48 | 49 | dl:hover { 50 | background: lighten(#323842, 10%); 51 | dt { 52 | color: #FFF; 53 | } 54 | } 55 | 56 | dl.selected { 57 | background: #323842; 58 | dd { 59 | display: block; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /css/widget/reset.less: -------------------------------------------------------------------------------- 1 | // 重置样式 2 | 3 | * { 4 | margin:0; 5 | padding:0; 6 | list-style:none 7 | } 8 | blockquote, 9 | body, 10 | button, 11 | dd, 12 | dl, 13 | dt, 14 | fieldset, 15 | form, 16 | h1, 17 | h2, 18 | h3, 19 | h4, 20 | h5, 21 | h6, 22 | hr, 23 | input, 24 | legend, 25 | li, 26 | ol, 27 | p, 28 | pre, 29 | td, 30 | textarea, 31 | th, 32 | ul { 33 | margin:0; 34 | padding:0 35 | } 36 | body, 37 | button, 38 | input, 39 | select, 40 | textarea { 41 | font:12px/1.5 tahoma,arial,sans-serif 42 | } 43 | h1, 44 | h2, 45 | h3, 46 | h4, 47 | h5, 48 | h6 { 49 | font-size:100% 50 | } 51 | address, 52 | cite, 53 | dfn, 54 | em, 55 | var { 56 | font-style:normal 57 | } 58 | code, 59 | kbd, 60 | pre, 61 | samp { 62 | font-family:courier new,courier,monospace 63 | } 64 | small { 65 | font-size:12px 66 | } 67 | ol,ul { 68 | list-style:none 69 | } 70 | a { 71 | text-decoration:none 72 | } 73 | a:hover { 74 | text-decoration:underline 75 | } 76 | legend { 77 | color:#000 78 | } 79 | fieldset, 80 | img { 81 | border:0 82 | } 83 | button, 84 | input, 85 | select, 86 | textarea { 87 | font-size:100% 88 | } 89 | 90 | table { 91 | border-collapse:collapse; 92 | border-spacing:0 93 | } 94 | -------------------------------------------------------------------------------- /css/widget/util.less: -------------------------------------------------------------------------------- 1 | 2 | .ellipsis() { 3 | overflow: hidden; 4 | text-overflow: ellipsis; 5 | white-space: nowrap; 6 | } 7 | 8 | .ellipsis(@width) when (@width > 0px) { 9 | width: @width; 10 | overflow: hidden; 11 | text-overflow: ellipsis; 12 | white-space: nowrap; 13 | } 14 | 15 | // 各种渐变 16 | .gradient-horizontal(@startColor: #555, @endColor: #333) { 17 | background-color: @endColor; 18 | background-image: -moz-linear-gradient(left, @startColor, @endColor); // FF 3.6+ 19 | background-image: -webkit-gradient(linear, 0 0, 100% 0, from(@startColor), to(@endColor)); // Safari 4+, Chrome 2+ 20 | background-image: -webkit-linear-gradient(left, @startColor, @endColor); // Safari 5.1+, Chrome 10+ 21 | background-image: -o-linear-gradient(left, @startColor, @endColor); // Opera 11.10 22 | background-image: linear-gradient(to right, @startColor, @endColor); // Standard, IE10 23 | background-repeat: repeat-x; 24 | } 25 | .gradient-vertical(@startColor: #555, @endColor: #333) { 26 | background-color: @endColor; 27 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), to(@endColor)); // Safari 4+, Chrome 2+ 28 | background-image: -webkit-linear-gradient(top, @startColor, @endColor); // Safari 5.1+, Chrome 10+ 29 | background-image: -moz-linear-gradient(top, @startColor, @endColor); // FF 3.6+ 30 | background-image: linear-gradient(to bottom, @startColor, @endColor); // Standard, IE10 31 | background-repeat: repeat-x; 32 | } 33 | .gradient-directional(@startColor: #555, @endColor: #333, @deg: 45deg) { 34 | background-color: @endColor; 35 | background-repeat: repeat-x; 36 | background-image: -webkit-linear-gradient(@deg, @startColor, @endColor); // Safari 5.1+, Chrome 10+ 37 | background-image: -moz-linear-gradient(@deg, @startColor, @endColor); // FF 3.6+ 38 | background-image: linear-gradient(@deg, @startColor, @endColor); // Standard, IE10 39 | } 40 | -------------------------------------------------------------------------------- /demo/bezierSegmentCross.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 二次贝塞尔曲线绘制 6 | 7 | 8 | 35 | 36 | 37 |
38 |
39 |
40 |
41 |
42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /demo/bezierSplit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 贝塞尔分割 6 | 7 | 34 | 35 | 36 |
37 |
38 |
39 |
40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /demo/boundSegmentCross.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | bound和线段相交 6 | 7 | 25 | 26 | 27 |
28 |
29 |
30 |
31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /demo/canvasFindBreakPoint.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | canvas查找关键点 6 | 7 | 34 | 35 | 36 |
37 | 38 | 灰度阈值: 39 | 40 | 41 | 42 | 高斯平滑: 43 | 44 | 45 | 46 | 47 | 48 | 49 |
50 | 51 |
52 |
53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /demo/contoursCombine.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 测试editor combine 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /demo/css/editor.less: -------------------------------------------------------------------------------- 1 | @import './reset.less'; 2 | @import '../../css/widget/ico.less'; 3 | @import '../../css/widget/glyf-editor.less'; 4 | 5 | body, html { 6 | height: 100%; 7 | } 8 | 9 | #render-view { 10 | font-size: 12px; 11 | width: 100%; 12 | height: 100%; 13 | position: relative; 14 | -webkit-text-size-adjust: none; 15 | display: block; 16 | } 17 | -------------------------------------------------------------------------------- /demo/css/editortest.less: -------------------------------------------------------------------------------- 1 | @import './editor.less'; 2 | 3 | .pan-left { 4 | float: left; 5 | width: 180px; 6 | background: #ECECEC; 7 | } 8 | 9 | #glyf-list { 10 | a { 11 | display: block; 12 | line-height: 24px; 13 | } 14 | } 15 | 16 | #render-view { 17 | margin-left: 200px; 18 | width: auto; 19 | } 20 | -------------------------------------------------------------------------------- /demo/css/render.less: -------------------------------------------------------------------------------- 1 | @import './reset.less'; 2 | 3 | body, html { 4 | height: 100%; 5 | } 6 | 7 | #render-view { 8 | width: 100%; 9 | height: 100%; 10 | min-height: 600px; 11 | position: relative; 12 | -webkit-text-size-adjust: none; 13 | } 14 | -------------------------------------------------------------------------------- /demo/css/reset.less: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | ul { 6 | list-style: none; 7 | } 8 | a { 9 | color: #03C; 10 | text-decoration: none; 11 | } 12 | a:hover { 13 | text-decoration: underline; 14 | } 15 | 16 | .hide { 17 | display: none; 18 | } -------------------------------------------------------------------------------- /demo/data/contours-1.js: -------------------------------------------------------------------------------- 1 | module.exports = {"name":"uniE016","unicode":[57366],"xMin":146,"xMax":573,"yMin":119,"yMax":546,"leftSideBearing":146,"advanceWidth":926,"contours":[[{"x":146,"y":490,"onCurve":true},{"x":146,"y":518},{"x":165,"y":531,"onCurve":true},{"x":177,"y":543},{"x":202,"y":546,"onCurve":true},{"x":517,"y":546,"onCurve":true},{"x":542,"y":543},{"x":561,"y":531,"onCurve":true},{"x":573,"y":518},{"x":573,"y":490,"onCurve":true},{"x":573,"y":175,"onCurve":true},{"x":573,"y":153},{"x":561,"y":138,"onCurve":true},{"x":542,"y":125},{"x":517,"y":119,"onCurve":true},{"x":202,"y":119,"onCurve":true},{"x":177,"y":125},{"x":165,"y":138,"onCurve":true},{"x":146,"y":153},{"x":146,"y":175,"onCurve":true},{"x":146,"y":490,"onCurve":true}]]}; 2 | -------------------------------------------------------------------------------- /demo/data/contours-10.js: -------------------------------------------------------------------------------- 1 | module.exports = {"xMin":99,"yMin":129,"xMax":243,"yMax":327,"unicode":[57357],"advanceWidth":633,"leftSideBearing":99,"name":"uniE00D","contours":[[{"x":186,"y":298},{"x":213,"y":289},{"x":232,"y":273},{"x":243,"y":251},{"x":243,"y":227},{"x":232,"y":206},{"x":213,"y":190},{"x":186,"y":181},{"x":157,"y":181},{"x":130,"y":190},{"x":111,"y":206},{"x":99,"y":227},{"x":99,"y":251},{"x":111,"y":273},{"x":130,"y":289},{"x":157,"y":298}],[{"x":99,"y":327,"onCurve":true},{"x":243,"y":327,"onCurve":true},{"x":243,"y":129,"onCurve":true},{"x":99,"y":129,"onCurve":true}]]}; 2 | -------------------------------------------------------------------------------- /demo/data/contours-11.js: -------------------------------------------------------------------------------- 1 | module.exports = {"xMin":25,"xMax":586,"yMin":137,"yMax":588,"advanceWidth":530,"unicode":[57395],"name":"uniE033","leftSideBearing":25,"contours":[[{"x":25,"y":137,"onCurve":true},{"x":333,"y":588},{"x":277,"y":221,"onCurve":true},{"x":586,"y":194,"onCurve":true}],[{"x":186,"y":280,"onCurve":true},{"x":422,"y":249,"onCurve":true},{"x":25,"y":192,"onCurve":true}]]} 2 | -------------------------------------------------------------------------------- /demo/data/contours-12.js: -------------------------------------------------------------------------------- 1 | module.exports = {"xMin":98,"xMax":420,"yMin":149,"yMax":424,"advanceWidth":530,"unicode":[57395],"name":"uniE033","leftSideBearing":98,"contours":[[{"x":98,"y":424,"onCurve":true},{"x":348,"y":424,"onCurve":true},{"x":348,"y":235,"onCurve":true},{"x":98,"y":235,"onCurve":true}],[{"x":170,"y":338,"onCurve":true},{"x":420,"y":338,"onCurve":true},{"x":420,"y":149,"onCurve":true},{"x":170,"y":149,"onCurve":true}]]}; 2 | -------------------------------------------------------------------------------- /demo/data/contours-2.js: -------------------------------------------------------------------------------- 1 | module.exports = {"xMin":26,"yMin":-80,"xMax":601,"yMax":263,"unicode":[57357],"advanceWidth":633,"leftSideBearing":26,"name":"uniE00D","contours":[[{"x":394,"y":138},{"x":339,"y":5},{"x":164,"y":263},{"x":79,"y":165,"onCurve":true},{"x":286,"y":4,"onCurve":true},{"x":385,"y":-80},{"x":422,"y":83},{"x":422,"y":158},{"x":369,"y":211},{"x":295,"y":211}],[{"x":597,"y":61,"onCurve":true},{"x":597,"y":62},{"x":595,"y":63},{"x":594,"y":64,"onCurve":true},{"x":586,"y":78},{"x":572,"y":84,"onCurve":true},{"x":459,"y":177},{"x":312,"y":177,"onCurve":true},{"x":163,"y":177},{"x":49,"y":81,"onCurve":true},{"x":42,"y":76},{"x":37,"y":70,"onCurve":true},{"x":36,"y":69},{"x":33,"y":66},{"x":33,"y":65,"onCurve":true},{"x":26,"y":53},{"x":26,"y":39,"onCurve":true},{"x":26,"y":19},{"x":55,"y":-10},{"x":75,"y":-10,"onCurve":true},{"x":91,"y":-10},{"x":105,"y":0,"onCurve":true},{"x":193,"y":80},{"x":312,"y":80,"onCurve":true},{"x":435,"y":80},{"x":524,"y":-4,"onCurve":true},{"x":526,"y":-2,"onCurve":true},{"x":538,"y":-10},{"x":552,"y":-10,"onCurve":true},{"x":573,"y":-10},{"x":601,"y":19},{"x":601,"y":39,"onCurve":true},{"x":601,"y":50},{"x":596,"y":60,"onCurve":true}]]}; 2 | -------------------------------------------------------------------------------- /demo/data/contours-3.js: -------------------------------------------------------------------------------- 1 | module.exports = {"xMin":41,"yMin":53,"xMax":748,"yMax":563,"unicode":[57357],"advanceWidth":633,"leftSideBearing":41,"name":"uniE00D","contours":[[{"x":241,"y":276,"onCurve":true},{"x":744,"y":563},{"x":150,"y":102,"onCurve":true}],[{"x":237,"y":325,"onCurve":true},{"x":748,"y":53},{"x":41,"y":309,"onCurve":true}]]} 2 | -------------------------------------------------------------------------------- /demo/data/contours-4.js: -------------------------------------------------------------------------------- 1 | module.exports = {"xMin":63,"yMin":176,"xMax":278,"yMax":406,"unicode":[57357],"advanceWidth":633,"leftSideBearing":63,"name":"uniE00D","contours":[[{"x":198,"y":176},{"x":214,"y":243},{"x":187,"y":337},{"x":131,"y":404},{"x":79,"y":404},{"x":63,"y":337},{"x":90,"y":243},{"x":146,"y":176}],[{"x":197,"y":177},{"x":247,"y":244},{"x":278,"y":339},{"x":272,"y":406},{"x":231,"y":406},{"x":181,"y":339},{"x":150,"y":244},{"x":156,"y":177}]]} 2 | -------------------------------------------------------------------------------- /demo/data/contours-5.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = {"xMin":66,"yMin":22,"xMax":500,"yMax":430,"unicode":[57357],"advanceWidth":633,"leftSideBearing":66,"name":"uniE00D","contours":[[{"x":226,"y":255},{"x":272,"y":301},{"x":272,"y":367},{"x":226,"y":413},{"x":160,"y":413},{"x":114,"y":367},{"x":114,"y":301},{"x":160,"y":255}],[{"x":431,"y":242},{"x":486,"y":297},{"x":486,"y":375},{"x":431,"y":430},{"x":353,"y":430},{"x":298,"y":375},{"x":298,"y":297},{"x":353,"y":242}],[{"x":196,"y":22},{"x":250,"y":76},{"x":250,"y":152},{"x":196,"y":206},{"x":119,"y":206},{"x":66,"y":152},{"x":66,"y":76},{"x":119,"y":22}],[{"x":178,"y":340,"onCurve":true},{"x":423,"y":340,"onCurve":true},{"x":423,"y":113,"onCurve":true},{"x":178,"y":113,"onCurve":true}],[{"x":446,"y":28},{"x":500,"y":82},{"x":500,"y":158},{"x":446,"y":212},{"x":369,"y":212},{"x":316,"y":158},{"x":316,"y":82},{"x":369,"y":28}]]} -------------------------------------------------------------------------------- /demo/data/contours-6.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = {"xMin":55,"yMin":-7,"xMax":494,"yMax":430,"unicode":[57357],"advanceWidth":633,"leftSideBearing":55,"name":"uniE00D","contours":[[{"x":319,"y":259},{"x":342,"y":282,"onCurve":true},{"x":365,"y":305},{"x":365,"y":338,"onCurve":true},{"x":365,"y":371},{"x":342,"y":394,"onCurve":true},{"x":319,"y":417},{"x":286,"y":417,"onCurve":true},{"x":253,"y":417},{"x":230,"y":394,"onCurve":true},{"x":207,"y":371},{"x":207,"y":338,"onCurve":true},{"x":207,"y":305},{"x":230,"y":282,"onCurve":true},{"x":253,"y":259},{"x":286,"y":259,"onCurve":true}],[{"x":431,"y":242},{"x":459,"y":270,"onCurve":true},{"x":486,"y":297},{"x":486,"y":336,"onCurve":true},{"x":486,"y":375},{"x":459,"y":403,"onCurve":true},{"x":431,"y":430},{"x":392,"y":430,"onCurve":true},{"x":353,"y":430},{"x":326,"y":403,"onCurve":true},{"x":298,"y":375},{"x":298,"y":336,"onCurve":true},{"x":298,"y":297},{"x":326,"y":270,"onCurve":true},{"x":353,"y":242},{"x":392,"y":242,"onCurve":true}],[{"x":185,"y":6},{"x":212,"y":33,"onCurve":true},{"x":239,"y":60},{"x":239,"y":98,"onCurve":true},{"x":239,"y":136},{"x":212,"y":163,"onCurve":true},{"x":185,"y":190},{"x":147,"y":190,"onCurve":true},{"x":108,"y":190},{"x":82,"y":163,"onCurve":true},{"x":55,"y":136},{"x":55,"y":98,"onCurve":true},{"x":55,"y":60},{"x":82,"y":33,"onCurve":true},{"x":108,"y":6},{"x":147,"y":6,"onCurve":true}],[{"x":178,"y":340,"onCurve":true},{"x":423,"y":340,"onCurve":true},{"x":423,"y":113,"onCurve":true},{"x":178,"y":113,"onCurve":true}],[{"x":435,"y":-7},{"x":465,"y":23,"onCurve":true},{"x":494,"y":52},{"x":494,"y":94,"onCurve":true},{"x":494,"y":135},{"x":465,"y":165,"onCurve":true},{"x":435,"y":194},{"x":393,"y":194,"onCurve":true},{"x":351,"y":194},{"x":322,"y":165,"onCurve":true},{"x":293,"y":135},{"x":293,"y":94,"onCurve":true},{"x":293,"y":52},{"x":322,"y":23,"onCurve":true},{"x":351,"y":-7},{"x":393,"y":-7,"onCurve":true}]]} 3 | -------------------------------------------------------------------------------- /demo/data/contours-7.js: -------------------------------------------------------------------------------- 1 | module.exports = {"xMin":-21,"yMin":176,"xMax":355,"yMax":407,"unicode":[57357],"advanceWidth":633,"leftSideBearing":-21,"name":"uniE00D","contours":[[{"x":198,"y":176},{"x":206,"y":210,"onCurve":true},{"x":214,"y":243},{"x":201,"y":290,"onCurve":true},{"x":187,"y":337},{"x":159,"y":371,"onCurve":true},{"x":131,"y":404},{"x":105,"y":404,"onCurve":true},{"x":79,"y":404},{"x":71,"y":371,"onCurve":true},{"x":63,"y":337},{"x":77,"y":290,"onCurve":true},{"x":90,"y":243},{"x":118,"y":210,"onCurve":true},{"x":146,"y":176},{"x":172,"y":176,"onCurve":true}],[{"x":128,"y":178},{"x":153,"y":212,"onCurve":true},{"x":178,"y":245},{"x":194,"y":293,"onCurve":true},{"x":209,"y":340},{"x":206,"y":374,"onCurve":true},{"x":203,"y":407},{"x":183,"y":407,"onCurve":true},{"x":162,"y":407},{"x":137,"y":374,"onCurve":true},{"x":112,"y":340},{"x":97,"y":293,"onCurve":true},{"x":81,"y":245},{"x":84,"y":212,"onCurve":true},{"x":87,"y":178},{"x":108,"y":178,"onCurve":true}],[{"x":245,"y":265},{"x":355,"y":286},{"x":355,"y":315},{"x":245,"y":336},{"x":88,"y":336},{"x":-21,"y":315},{"x":-21,"y":286},{"x":88,"y":265}]]} 2 | -------------------------------------------------------------------------------- /demo/data/contours-8.js: -------------------------------------------------------------------------------- 1 | module.exports = {"xMin":0,"yMin":0,"xMax":286,"yMax":266,"unicode":[58932],"advanceWidth":1024,"leftSideBearing":0,"name":"uniE634","contours":[[{"x":0,"y":81,"onCurve":true},{"x":0,"y":220,"onCurve":true},{"x":0,"y":230},{"x":8,"y":247},{"x":22,"y":260},{"x":41,"y":266},{"x":51,"y":266,"onCurve":true},{"x":237,"y":266,"onCurve":true},{"x":249,"y":266},{"x":266,"y":259},{"x":279,"y":247},{"x":286,"y":232},{"x":286,"y":224,"onCurve":true},{"x":286,"y":81,"onCurve":true}],[{"x":286,"y":185,"onCurve":true,"index0":13,"index1":13},{"x":286,"y":43,"onCurve":true,"index0":0,"index1":0},{"x":286,"y":34},{"x":279,"y":19},{"x":266,"y":7},{"x":249,"y":0},{"x":237,"y":0,"onCurve":true,"index0":6,"index1":6},{"x":51,"y":0,"onCurve":true,"index0":6,"index1":6},{"x":41,"y":0},{"x":22,"y":7},{"x":8,"y":19},{"x":0,"y":36},{"x":0,"y":47,"onCurve":true,"index0":12,"index1":12},{"x":0,"y":185,"onCurve":true,"index0":13,"index1":13}]]} 2 | -------------------------------------------------------------------------------- /demo/data/contours-9.js: -------------------------------------------------------------------------------- 1 | module.exports = {"xMin":26,"yMin":-80,"xMax":601,"yMax":248,"unicode":[57357],"advanceWidth":633,"leftSideBearing":26,"name":"uniE00D","contours":[[{"x":225,"y":168,"onCurve":true},{"x":151,"y":248},{"x":79,"y":165,"onCurve":true},{"x":123,"y":131,"onCurve":true},{"x":84,"y":111},{"x":49,"y":81,"onCurve":true},{"x":42,"y":76},{"x":37,"y":70,"onCurve":true},{"x":36,"y":69},{"x":33,"y":66},{"x":33,"y":65,"onCurve":true},{"x":26,"y":53},{"x":26,"y":39,"onCurve":true},{"x":26,"y":19},{"x":55,"y":-10},{"x":75,"y":-10,"onCurve":true},{"x":91,"y":-10},{"x":105,"y":0,"onCurve":true},{"x":153,"y":43},{"x":210,"y":63,"onCurve":true},{"x":286,"y":4,"onCurve":true},{"x":385,"y":-80},{"x":404,"y":2,"onCurve":true},{"x":411,"y":36},{"x":416,"y":63,"onCurve":true},{"x":475,"y":42},{"x":524,"y":-4,"onCurve":true},{"x":526,"y":-2,"onCurve":true},{"x":538,"y":-10},{"x":552,"y":-10,"onCurve":true},{"x":573,"y":-10},{"x":601,"y":19},{"x":601,"y":39,"onCurve":true},{"x":601,"y":50},{"x":596,"y":60,"onCurve":true},{"x":597,"y":61,"onCurve":true},{"x":597,"y":62},{"x":595,"y":63},{"x":594,"y":64,"onCurve":true},{"x":586,"y":78},{"x":572,"y":84,"onCurve":true},{"x":498,"y":145},{"x":410,"y":166,"onCurve":true},{"x":404,"y":176},{"x":396,"y":185,"onCurve":true},{"x":369,"y":211},{"x":332,"y":211,"onCurve":true},{"x":296,"y":211},{"x":343,"y":176,"onCurve":true},{"x":327,"y":177},{"x":312,"y":177,"onCurve":true},{"x":267,"y":177}]]}; 2 | -------------------------------------------------------------------------------- /demo/data/contours.js: -------------------------------------------------------------------------------- 1 | module.exports = {"xMin":0,"yMin":-32,"xMax":512,"yMax":561,"unicode":[57358],"advanceWidth":512,"leftSideBearing":0,"name":"uniE00E","contours":[[{"x":362,"y":480},{"x":150,"y":480},{"x":0,"y":330},{"x":0,"y":118},{"x":150,"y":-32},{"x":362,"y":-32},{"x":512,"y":118},{"x":512,"y":330}],[{"x":376,"y":379,"onCurve":true},{"x":381,"y":374},{"x":381,"y":358},{"x":376,"y":353,"onCurve":true},{"x":367,"y":344,"onCurve":true},{"x":361,"y":339},{"x":346,"y":339},{"x":341,"y":344,"onCurve":true},{"x":270,"y":415,"onCurve":true},{"x":199,"y":345,"onCurve":true},{"x":194,"y":339},{"x":179,"y":339},{"x":173,"y":345,"onCurve":true},{"x":164,"y":353,"onCurve":true},{"x":159,"y":359},{"x":159,"y":374},{"x":164,"y":379,"onCurve":true},{"x":235,"y":450,"onCurve":true},{"x":164,"y":521,"onCurve":true},{"x":159,"y":526},{"x":159,"y":542},{"x":164,"y":547,"onCurve":true},{"x":173,"y":556,"onCurve":true},{"x":178,"y":561},{"x":193,"y":561},{"x":199,"y":556,"onCurve":true},{"x":270,"y":485,"onCurve":true},{"x":341,"y":556,"onCurve":true},{"x":346,"y":561},{"x":362,"y":561},{"x":367,"y":556,"onCurve":true},{"x":376,"y":547,"onCurve":true},{"x":381,"y":542},{"x":381,"y":526},{"x":376,"y":521,"onCurve":true},{"x":305,"y":450,"onCurve":true}]]}; 2 | -------------------------------------------------------------------------------- /demo/datastore.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 测试editor 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /demo/editor-iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 测试editor远程调用 6 | 7 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /demo/editor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 测试editor 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /demo/editortest.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 测试editor 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 21 | 25 |
26 |
27 | 28 |
29 |
30 |
31 | 32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /demo/findBreakPoints.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 查找拐点 6 | 7 | 63 | 64 | 65 |
66 |
67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /demo/fitContours.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | canvas读取图片 6 | 7 | 42 | 43 | 44 |
45 | 46 |
47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /demo/fitCurve.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 三次bezier曲线拟合 6 | 7 | 34 | 35 | 36 |
37 |
38 |
39 |
40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /demo/fitImage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | canvas拟合图像 6 | 7 | 8 | 26 | 27 | 28 |
29 | 30 | 灰度阈值: 31 |
32 | 33 | 34 |
35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | demo首页 6 | 7 | 8 | 9 |

demo列表

10 | 11 |
  • bezierSegmentCross
  • 12 |
  • bezierSplit
  • 13 |
  • boundSegmentCross
  • 14 |
  • CanvasFindBreakPoint
  • 15 |
  • contoursCombine
  • 16 |
  • editor-iframe
  • 17 |
  • editor
  • 18 |
  • editortest
  • 19 |
  • fitContours
  • 20 |
  • findBreakPoints
  • 21 |
  • fitCurve
  • 22 |
  • fitImage
  • 23 |
  • procImage
  • 24 |
  • quadraticBezierCross
  • 25 |
  • reducePoints
  • 26 |
  • render
  • 27 |
  • math
  • 28 |
  • segmentCross
  • 29 |
    30 | 31 | -------------------------------------------------------------------------------- /demo/js/contoursCombine.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file contoursCombine.js 3 | * @author mengke01 4 | * @date 5 | * @description 6 | * 路径合并,求交 7 | */ 8 | 9 | import lang from 'common/lang'; 10 | import editor from 'editor/main'; 11 | const shape_baidu = require('../data/contours-2'); 12 | 13 | let currentEditor; 14 | 15 | const entry = { 16 | 17 | /** 18 | * 初始化 19 | */ 20 | init() { 21 | let clonedShape = lang.clone(shape_baidu); 22 | 23 | window.editor = currentEditor = editor.create($('#render-view').get(0)); 24 | currentEditor.setFont(clonedShape); 25 | let jointLayer = currentEditor.fontLayer; 26 | let paths = currentEditor.fontLayer.shapes.map(function (shape) { 27 | return shape.points; 28 | }); 29 | } 30 | }; 31 | 32 | entry.init(); 33 | -------------------------------------------------------------------------------- /demo/js/datastore.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 测试DataStore 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import DataStore from 'common/DataStore'; 7 | 8 | 9 | const entry = { 10 | 11 | /** 12 | * 初始化 13 | */ 14 | init() { 15 | 16 | let store = new DataStore({ 17 | name: 'test', 18 | storeName: 'test-project' 19 | }); 20 | 21 | store.open(function (e) { 22 | 23 | store.add(1001, 'sssss', function () { 24 | store.clear(); 25 | store.close(); 26 | }); 27 | 28 | }, function (e) { 29 | console.log(e); 30 | }); 31 | 32 | let store1 = new DataStore({ 33 | name: 'test', 34 | storeName: 'test-project-1' 35 | }); 36 | 37 | store1.open(function () { 38 | store.removeStore('test-project-1'); 39 | store.close(); 40 | }); 41 | } 42 | }; 43 | 44 | entry.init(); 45 | -------------------------------------------------------------------------------- /demo/js/editor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file render.js 3 | * @author mengke01 4 | * @date 5 | * @description 6 | * editor 组件测试 7 | */ 8 | 9 | import editor from 'editor/main'; 10 | const shape_baidu = require('../data/contours-1'); 11 | 12 | let currentEditor; 13 | const entry = { 14 | 15 | /** 16 | * 初始化 17 | */ 18 | init() { 19 | currentEditor = editor.create($('#render-view').get(0)); 20 | window.editor = currentEditor.setFont(shape_baidu); 21 | //currentEditor.blur(); 22 | 23 | } 24 | }; 25 | 26 | entry.init(); -------------------------------------------------------------------------------- /demo/js/fitContours.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 寻找关键点 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import fitContour from 'graphics/image/contour/fitContour'; 7 | import drawPath from 'render/util/drawPath'; 8 | import pathUtil from 'graphics/pathUtil'; 9 | 10 | const data = require('../data/image-contours5'); 11 | const entry = { 12 | 13 | /** 14 | * 初始化 15 | */ 16 | init() { 17 | 18 | let html = ''; 19 | let contours = []; 20 | data.forEach(function (points) { 21 | 22 | points.forEach(function (p) { 23 | html += ''; 24 | }); 25 | 26 | points = pathUtil.scale(points, 2); 27 | contours.push(pathUtil.scale(fitContour(points, 2), 0.5)); 28 | points = pathUtil.scale(points, 0.5); 29 | }); 30 | 31 | 32 | $('#points').html(html); 33 | 34 | html = ''; 35 | 36 | let ctx = $('#canvas').get(0).getContext('2d'); 37 | ctx.strokeStyle = 'pink'; 38 | 39 | contours.forEach(function (contour) { 40 | for (let i = 0, l = contour.length; i < l; i++) { 41 | html += ''; 42 | } 43 | drawPath(ctx, contour); 44 | }); 45 | 46 | ctx.stroke(); 47 | $('#points-break').html(html); 48 | } 49 | }; 50 | 51 | entry.init(); 52 | -------------------------------------------------------------------------------- /demo/js/math.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file math.js 3 | * @author mengke01 4 | * @date 5 | * @description 6 | * 方程运算 7 | */ 8 | 9 | import quadraticEquation from 'math/quadraticEquation'; 10 | import cubeEquation from 'math/cubeEquation'; 11 | import quarticEquation from 'math/quarticEquation'; 12 | 13 | import bezierQ2Equation from 'math/bezierQ2Equation'; 14 | import bezierCubeEquation from 'math/bezierCubeEquation'; 15 | import bezierQ4Equation from 'math/bezierQ4Equation'; 16 | 17 | const entry = { 18 | 19 | /** 20 | * 初始化 21 | */ 22 | init() { 23 | 24 | console.log(quadraticEquation(1, 0, 1)); 25 | console.log(bezierQ2Equation(1, 0, 1)); 26 | console.log('--------------------------------'); 27 | console.log(quadraticEquation(1, -2, 1)); 28 | console.log(bezierQ2Equation(1, -2, 1)); 29 | console.log('--------------------------------'); 30 | 31 | 32 | 33 | 34 | console.log(cubeEquation(1, 0, 0, 1)); 35 | console.log(bezierCubeEquation(1, 0, 0, 1)); 36 | console.log('--------------------------------'); 37 | console.log(cubeEquation(1, 0, 0, -1)); 38 | console.log(bezierCubeEquation(1, 0, 0, -1)); 39 | console.log('--------------------------------'); 40 | 41 | 42 | 43 | 44 | console.log(quarticEquation(1, 0, 0, 0, 1)); 45 | console.log(bezierQ4Equation(1, 0, 0, 0, 1)); 46 | console.log('--------------------------------'); 47 | console.log(quarticEquation(1, 0, 0, 0, -1)); 48 | console.log(bezierQ4Equation(1, 0, 0, 0, -1)); 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | } 62 | }; 63 | 64 | entry.init(); -------------------------------------------------------------------------------- /demo/js/reducePoints.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 寻找关键点 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import reducePoints from 'graphics/image/contour/douglasPeuckerReducePoints'; 7 | import pathUtil from 'graphics/pathUtil'; 8 | 9 | const data = require('../data/image-contours10'); 10 | 11 | const entry = { 12 | 13 | /** 14 | * 初始化 15 | */ 16 | init() { 17 | let breakPoints = []; 18 | let html = ''; 19 | 20 | data.forEach(function(c) { 21 | c.splice(c.length - 1, 1); 22 | }); 23 | 24 | data.forEach(function (contour) { 25 | contour.forEach(function(p) { 26 | html += ''; 27 | }); 28 | }); 29 | 30 | $('#points').html(html); 31 | 32 | data.forEach(function (contour) { 33 | pathUtil.scale(contour, 2); 34 | let points = reducePoints(contour, 0, contour.length - 1, 2); 35 | if (points) { 36 | points.forEach(function (p) { 37 | breakPoints.push(p); 38 | }); 39 | } 40 | pathUtil.scale(contour, 0.5); 41 | }); 42 | 43 | 44 | html = ''; 45 | for (let i = 0, l = breakPoints.length; i < l; i++) { 46 | let c = "break"; 47 | if (breakPoints[i].tangency) { 48 | c = 'tangency'; 49 | } 50 | else if(breakPoints[i].inflexion) { 51 | c = 'inflexion'; 52 | } 53 | let width = ''; 54 | if (breakPoints[i].right == 1) { 55 | width = 'width: 4px;height: 4px'; 56 | } 57 | html += ''; 58 | } 59 | 60 | $('#points-break').html(html); 61 | } 62 | }; 63 | 64 | entry.init(); 65 | -------------------------------------------------------------------------------- /demo/math.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 测试math函数 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /demo/paper-boolean.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | paperjs path boolean操作 6 | 7 | 8 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /demo/quadraticBezierCross.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 二次贝塞尔求交 6 | 7 | 34 | 35 | 36 |
    37 |
    38 |
    39 |
    40 |
    41 |
    42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /demo/reducePoints.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 消减中间点 6 | 7 | 48 | 49 | 50 |
    51 |
    52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /demo/render.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 测试render 6 | 7 | 8 | 9 | 10 | 11 |
    12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /demo/segmentCross.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 线段相交 6 | 7 | 25 | 26 | 27 |
    28 |
    29 |
    30 |
    31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /demo/sync/icon.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @file icon.css 3 | */ 4 | 5 | @font-face { 6 | font-family: "fonteditor"; 7 | src: url("fonteditor.eot"); /* IE9 */ 8 | src: url("fonteditor.eot?#iefix") format("embedded-opentype"), /* IE6-IE8 */ 9 | url("fonteditor.woff") format("woff"), /* chrome、firefox */ 10 | url("fonteditor.ttf") format("truetype"), /* chrome、firefox、opera、Safari, Android, iOS 4.2+ */ 11 | url("fonteditor.svg#uxfonteditor") format("svg"); /* iOS 4.1- */ 12 | } 13 | 14 | .icon { 15 | font-family: "fonteditor"; 16 | speak: none; 17 | font-style: normal; 18 | font-weight: normal; 19 | font-variant: normal; 20 | text-transform: none; 21 | line-height: 1; 22 | -webkit-font-smoothing: antialiased; 23 | -moz-osx-font-smoothing: grayscale; 24 | } 25 | -------------------------------------------------------------------------------- /demo/test/a.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fonteditor/dd7cc302bb3a742ca5821d9f3103a4fcdfdf6588/demo/test/a.gif -------------------------------------------------------------------------------- /demo/test/add.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fonteditor/dd7cc302bb3a742ca5821d9f3103a4fcdfdf6588/demo/test/add.bmp -------------------------------------------------------------------------------- /demo/test/add.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fonteditor/dd7cc302bb3a742ca5821d9f3103a4fcdfdf6588/demo/test/add.jpg -------------------------------------------------------------------------------- /demo/test/circle.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fonteditor/dd7cc302bb3a742ca5821d9f3103a4fcdfdf6588/demo/test/circle.gif -------------------------------------------------------------------------------- /demo/test/circle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fonteditor/dd7cc302bb3a742ca5821d9f3103a4fcdfdf6588/demo/test/circle.jpg -------------------------------------------------------------------------------- /demo/test/cl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fonteditor/dd7cc302bb3a742ca5821d9f3103a4fcdfdf6588/demo/test/cl.gif -------------------------------------------------------------------------------- /demo/test/cur.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fonteditor/dd7cc302bb3a742ca5821d9f3103a4fcdfdf6588/demo/test/cur.jpg -------------------------------------------------------------------------------- /demo/test/ecomfe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fonteditor/dd7cc302bb3a742ca5821d9f3103a4fcdfdf6588/demo/test/ecomfe.png -------------------------------------------------------------------------------- /demo/test/oval.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fonteditor/dd7cc302bb3a742ca5821d9f3103a4fcdfdf6588/demo/test/oval.gif -------------------------------------------------------------------------------- /demo/test/qgqc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fonteditor/dd7cc302bb3a742ca5821d9f3103a4fcdfdf6588/demo/test/qgqc.jpg -------------------------------------------------------------------------------- /demo/test/question.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fonteditor/dd7cc302bb3a742ca5821d9f3103a4fcdfdf6588/demo/test/question.png -------------------------------------------------------------------------------- /demo/test/sjx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fonteditor/dd7cc302bb3a742ca5821d9f3103a4fcdfdf6588/demo/test/sjx.jpg -------------------------------------------------------------------------------- /demo/test/unicodenames.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fonteditor/dd7cc302bb3a742ca5821d9f3103a4fcdfdf6588/demo/test/unicodenames.xlsx -------------------------------------------------------------------------------- /dep/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fonteditor/dd7cc302bb3a742ca5821d9f3103a4fcdfdf6588/dep/favicon.ico -------------------------------------------------------------------------------- /dep/woff2/woff2.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fonteditor/dd7cc302bb3a742ca5821d9f3103a4fcdfdf6588/dep/woff2/woff2.wasm -------------------------------------------------------------------------------- /editor.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Editor 6 | 7 | 8 | 9 |
    10 | 11 | 12 | 13 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /empty.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | fonteditor 6 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /font/fonteditor.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fonteditor/dd7cc302bb3a742ca5821d9f3103a4fcdfdf6588/font/fonteditor.eot -------------------------------------------------------------------------------- /font/fonteditor.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fonteditor/dd7cc302bb3a742ca5821d9f3103a4fcdfdf6588/font/fonteditor.ttf -------------------------------------------------------------------------------- /font/fonteditor.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fonteditor/dd7cc302bb3a742ca5821d9f3103a4fcdfdf6588/font/fonteditor.woff -------------------------------------------------------------------------------- /fonteditor-zh.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fonteditor/dd7cc302bb3a742ca5821d9f3103a4fcdfdf6588/fonteditor-zh.jpg -------------------------------------------------------------------------------- /fonteditor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fonteditor/dd7cc302bb3a742ca5821d9f3103a4fcdfdf6588/fonteditor.jpg -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fonteditor", 3 | "version": "2.3.0", 4 | "repository": { 5 | "type": "git", 6 | "url": "git://github.com/ecomfe/fonteditor.git" 7 | }, 8 | "bugs": { 9 | "url": "https://github.com/ecomfe/fonteditor/issues" 10 | }, 11 | "author": "kekee000", 12 | "contributors": [ 13 | { 14 | "email": "kekee000@gmail.com", 15 | "name": "kekee000" 16 | }, 17 | { 18 | "email": "junmer@foxmail.com", 19 | "name": "junmer" 20 | } 21 | ], 22 | "description": "ttf to woff, woff2, svg, eot convert, ttf adjust", 23 | "scripts": { 24 | "start": "npm run dev", 25 | "dev": "webpack-dev-server --open --config ./config/webpack.dev.js", 26 | "demo": "webpack-dev-server --open --config ./config/webpack.demo.js", 27 | "prod": "webpack --production --config ./config/webpack.prod.js", 28 | "test": "./node_modules/.bin/mocha --require @babel/register test/spec/*.spec.js test/spec/**/*.spec.js", 29 | "build": "sh build.sh", 30 | "lint": "fecs ./src --reporter=baidu --rules" 31 | }, 32 | "devDependencies": { 33 | "@babel/cli": "^7.20.7", 34 | "@babel/core": "^7.20.12", 35 | "@babel/preset-env": "^7.20.2", 36 | "@babel/register": "^7.18.9", 37 | "babel-plugin-module-resolver": "^4.1.0", 38 | "css-loader": "^3.2.0", 39 | "file-loader": "^4.2.0", 40 | "fonteditor-core": "^2.3.2", 41 | "html-webpack-plugin": "^3.2.0", 42 | "less": "^2.0.0", 43 | "less-loader": "^5.0.0", 44 | "mini-css-extract-plugin": "^0.8.0", 45 | "mocha": "^6.2.2", 46 | "optimize-css-assets-webpack-plugin": "^5.0.3", 47 | "style-loader": "^1.0.0", 48 | "to-string-loader": "^1.1.5", 49 | "url-loader": "^2.2.0", 50 | "webpack": "^4.41.1", 51 | "webpack-cli": "^3.3.9", 52 | "webpack-dev-server": "^3.8.2" 53 | }, 54 | "homepage": "https://github.com/ecomfe/fonteditor", 55 | "keywords": [ 56 | "font", 57 | "ttf", 58 | "woff", 59 | "woff2", 60 | "svg", 61 | "eot" 62 | ], 63 | "license": "MIT" 64 | } 65 | -------------------------------------------------------------------------------- /php/readOnline.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | proxy 6 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/common/I18n.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 用于国际化的字符串管理类 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | export {default} from 'fonteditor-core/common/I18n'; 7 | -------------------------------------------------------------------------------- /src/common/ajaxFile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ajax获取文本数据 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | export {default} from 'fonteditor-core/common/ajaxFile'; 6 | -------------------------------------------------------------------------------- /src/common/getPixelRatio.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 获取当前设备的像素比率 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | const pixelRatio = (function (context) { 7 | let backingStore = context.backingStorePixelRatio 8 | || context.webkitBackingStorePixelRatio 9 | || context.mozBackingStorePixelRatio 10 | || context.msBackingStorePixelRatio 11 | || context.oBackingStorePixelRatio 12 | || context.backingStorePixelRatio 13 | || 1; 14 | 15 | return (window.devicePixelRatio || 1) / backingStore; 16 | })(HTMLCanvasElement.prototype); 17 | 18 | export default pixelRatio; 19 | -------------------------------------------------------------------------------- /src/common/lang.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 语言相关函数 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import { 7 | clone, 8 | overwrite, 9 | debounce, 10 | throttle, 11 | equals 12 | } from 'fonteditor-core/common/lang'; 13 | 14 | export default { 15 | clone, 16 | overwrite, 17 | debounce, 18 | throttle, 19 | equals, 20 | parseQuery(querystring) { 21 | let query = querystring.split('&') 22 | .map(function (item) { 23 | item = item.split('='); 24 | return [item[0], decodeURIComponent(item[1])]; 25 | }) 26 | .reduce(function (query, item) { 27 | query[item[0]] = item[1]; 28 | return query; 29 | }, {}); 30 | return query; 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /src/common/string.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 字符串相关的函数 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | export {default} from 'fonteditor-core/common/string'; 6 | -------------------------------------------------------------------------------- /src/editor/command/commandSupport.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 命令相关配置项目,用来配置命令执行前或者执行后的动作 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | export default { 7 | // 支持历史记录的命令列表 8 | history: { 9 | alignshapes: true, 10 | verticalalignshapes: true, 11 | horizontalalignshapes: true, 12 | joinshapes: true, 13 | intersectshapes: true, 14 | tangencyshapes: true, 15 | splitshapes: true, 16 | removeshapes: true, 17 | reversepoints: true, 18 | topshape: true, 19 | bottomshape: true, 20 | upshape: true, 21 | downshape: true, 22 | cutshapes: true, 23 | pasteshapes: true, 24 | addshapes: true, 25 | rotateleft: true, 26 | rotateright: true, 27 | flipshapes: true, 28 | mirrorshapes: true 29 | } 30 | }; 31 | 32 | -------------------------------------------------------------------------------- /src/editor/command/setSelectedCommand.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 设置选中的命令 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | /** 7 | * 设置选中的命令 8 | * 9 | * @param {Array} commandList 命令列表 10 | * @param {string} command 命令路径,支持二级路径 11 | * @param {boolean} selected 是否被选中 12 | * @return {boolean} 是否找到command 13 | */ 14 | export default function setSelectedCommand(commandList, command, selected) { 15 | let path = command.split('.'); 16 | for (let i = 0, l = commandList.length; i < l; i++) { 17 | if (commandList[i].name === path[0]) { 18 | if (!commandList[i].items) { 19 | commandList[i].selected = !!selected; 20 | return true; 21 | } 22 | else if (path[1]) { 23 | let items = commandList[i].items; 24 | for (let j = 0, ll = items.length; j < ll; j++) { 25 | if (items[j].name === path[1]) { 26 | items[j].selected = !!selected; 27 | return true; 28 | } 29 | } 30 | } 31 | } 32 | } 33 | 34 | return false; 35 | } 36 | 37 | -------------------------------------------------------------------------------- /src/editor/command/support.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 支持的命令列表 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import shape from './shape'; 7 | import transform from './transform'; 8 | import align from './align'; 9 | import join from './join'; 10 | import referenceline from './referenceline'; 11 | import editor from './editor'; 12 | 13 | export default { 14 | ...shape, 15 | ...transform, 16 | ...align, 17 | ...join, 18 | ...referenceline, 19 | ...shape, 20 | ...editor 21 | }; 22 | -------------------------------------------------------------------------------- /src/editor/controller/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file index 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import initSetting from './initSetting'; 7 | import initFont from './initFont'; 8 | import initRender from './initRender'; 9 | import initLayer from './initLayer'; 10 | import initAxis from './initAxis'; 11 | import initBinder from './initBinder'; 12 | 13 | // 默认editor的初始化函数列表,这里应该是按照特定顺序执行的函数集合 14 | export default [ 15 | initSetting, 16 | initFont, 17 | initRender, 18 | initLayer, 19 | initAxis, 20 | initBinder 21 | ]; 22 | -------------------------------------------------------------------------------- /src/editor/controller/initBinder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 初始化绑定器 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import commandSupport from '../command/commandSupport'; 7 | 8 | /** 9 | * 初始化绑定器 10 | */ 11 | export default function () { 12 | let me = this; 13 | // 保存历史记录 14 | me.on('change', function () { 15 | me.history.add(me.getShapes()); 16 | }); 17 | 18 | me.on('command', function (e) { 19 | if (false !== e.result && commandSupport.history[e.command]) { 20 | me.fire('change'); 21 | } 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /src/editor/controller/initLayer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Editor的layer初始化 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | /** 7 | * 初始化层 8 | */ 9 | export default function initLayer() { 10 | 11 | this.axisLayer = this.render.addLayer('axis', { 12 | level: 10, 13 | fill: false 14 | }); 15 | 16 | this.fontLayer = this.render.addLayer('font', Object.assign({ 17 | level: 20, 18 | lineWidth: 1, 19 | strokeColor: '#999', 20 | fillColor: '#555', 21 | strokeSeparate: false 22 | }, this.options.fontLayer)); 23 | 24 | this.coverLayer = this.render.addLayer('cover', Object.assign({ 25 | level: 30, 26 | fill: false, 27 | strokeColor: this.options.coverLayer.strokeColor, 28 | fillColor: this.options.coverLayer.fillColor 29 | }, this.options.coverLayer)); 30 | 31 | this.referenceLineLayer = this.render.addLayer('referenceline', { 32 | level: 40, 33 | fill: false, 34 | strokeColor: this.options.referenceline.style.strokeColor 35 | }); 36 | 37 | 38 | this.graduationLayer = this.render.addLayer('graduation', { 39 | level: 50, 40 | fill: false, 41 | disabled: true 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /src/editor/controller/initSetting.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 初始化Editor设置 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import setSelectedCommand from '../command/setSelectedCommand'; 7 | import commandList from '../menu/commandList'; 8 | import lang from 'common/lang'; 9 | 10 | function initSetting(options) { 11 | 12 | // 设置菜单选中状态 13 | 14 | setSelectedCommand( 15 | commandList.editor, 'setting.gridsorption', 16 | !!options.sorption.enableGrid 17 | ); 18 | 19 | setSelectedCommand( 20 | commandList.editor, 'setting.shapesorption', 21 | !!options.sorption.enableShape 22 | ); 23 | 24 | setSelectedCommand( 25 | commandList.editor, 'setting.showgrid', 26 | !!options.axis.showGrid 27 | ); 28 | } 29 | 30 | /** 31 | * 设置选项 32 | * 33 | * @param {Object} options 选项 34 | * @param {Object} options.sorption 吸附设置 35 | * @param {Object} options.axis 坐标设置 36 | * @see editor/options 37 | */ 38 | function setOptions(options) { 39 | 40 | this.execCommand('gridsorption', options.sorption.enableGrid); 41 | this.execCommand('shapesorption', options.sorption.enableShape); 42 | this.execCommand('showgrid', options.axis.showGrid); 43 | 44 | this.fontLayer.options.fill = !!options.fontLayer.fill; 45 | this.fontLayer.options.fillColor = options.fontLayer.fillColor; 46 | this.fontLayer.options.strokeColor = options.fontLayer.strokeColor; 47 | this.fontLayer.refresh(); 48 | 49 | this.axis.gapColor = options.axis.gapColor; 50 | this.axis.metricsColor = options.axis.metricsColor; 51 | this.axis.emColor = options.axis.emColor; 52 | this.axis.graduation.gap = options.axis.graduation.gap || 100; 53 | this.axisLayer.refresh(); 54 | this.graduationLayer.refresh(); 55 | lang.overwrite(this.options, options); 56 | } 57 | 58 | export default function () { 59 | initSetting.call(this, this.options); 60 | this.setOptions = setOptions; 61 | } 62 | -------------------------------------------------------------------------------- /src/editor/group/rotateTransform.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 旋转变换 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import getRotateMatrix from './getRotateMatrix'; 7 | import pathRotate from 'graphics/pathRotate'; 8 | import pathSkew from 'graphics/pathSkew'; 9 | import lang from 'common/lang'; 10 | 11 | /** 12 | * 旋转变换 13 | * 14 | * @param {Object} point 参考点 15 | * @param {Camera} camera 镜头对象 16 | */ 17 | export default function rotateTransform(point, camera) { 18 | 19 | let matrix = getRotateMatrix(point.pos, this.bound, camera); 20 | 21 | let transformer = point.pos <= 4 ? pathRotate : pathSkew; 22 | 23 | // 更新shape 24 | let shapes = this.shapes; 25 | 26 | this.coverShapes.forEach(function (coverShape, index) { 27 | let shape = lang.clone(shapes[index]); 28 | transformer(shape.points, matrix[2], matrix[0], matrix[1]); 29 | Object.assign(coverShape, shape); 30 | 31 | }); 32 | 33 | 34 | // 更新边界 35 | let coverLayer = this.editor.coverLayer; 36 | let boundShape = coverLayer.getShape('bound'); 37 | let bound = this.bound; 38 | boundShape.points = transformer( 39 | [ 40 | {x: bound.x, y: bound.y}, 41 | {x: bound.x + bound.width, y: bound.y}, 42 | {x: bound.x + bound.width, y: bound.y + bound.height}, 43 | {x: bound.x, y: bound.y + bound.height} 44 | ], 45 | matrix[2], matrix[0], matrix[1] 46 | ); 47 | 48 | // 更新中心点 49 | let boundCenter = coverLayer.getShape('boundcenter'); 50 | if (!boundCenter) { 51 | boundCenter = { 52 | type: 'cpoint', 53 | id: 'boundcenter', 54 | x: bound.x + bound.width / 2, 55 | y: bound.y + bound.height / 2 56 | }; 57 | coverLayer.addShape(boundCenter); 58 | } 59 | boundCenter.x = (boundShape.points[0].x + boundShape.points[2].x) / 2; 60 | boundCenter.y = (boundShape.points[0].y + boundShape.points[2].y) / 2; 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/editor/i18n/i18n.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 语言字符串管理 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import I18n from 'common/I18n'; 7 | import zhcn from './zh-cn'; 8 | import enus from './en-us'; 9 | 10 | export default new I18n( 11 | [ 12 | ['zh-cn', zhcn], 13 | ['en-us', enus] 14 | ], 15 | window.language 16 | ); 17 | -------------------------------------------------------------------------------- /src/editor/i18n/zh-cn.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file zh-cn 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | export default { 7 | copy: '复制', 8 | paste: '粘贴', 9 | cut: '剪切', 10 | del: '删除', 11 | save: '保存', 12 | undo: '撤销', 13 | redo: '重做', 14 | rangemode: '轮廓模式', 15 | pointmode: '点模式', 16 | order: '顺序', 17 | upshape: '上移一层', 18 | downshape: '下移一层', 19 | reversepoints: '改变方向', 20 | topshape: '置前', 21 | bottomshape: '置后', 22 | align: '水平方向', 23 | alignleft: '左对齐', 24 | aligncenter: '居中对齐', 25 | alignright: '右对齐', 26 | verticalalign: '垂直方向', 27 | alignshapes: '对齐形状', 28 | aligntop: '顶端对齐', 29 | alignmiddle: '垂直居中对齐', 30 | aligndescent: '底端对齐', 31 | alignbaseline: '基线对齐', 32 | rotateleft: '向左旋转', 33 | rotateright: '向右旋转', 34 | transform: '变换', 35 | flip: '翻转', 36 | mirror: '镜像', 37 | splitshapes: '切割轮廓', 38 | joinshapes: '结合', 39 | intersectshapes: '相交', 40 | tangencyshapes: '相切', 41 | removeshapes: '删除轮廓', 42 | adjustpos: '调整位置', 43 | adjustglyf: '调整字形', 44 | addpath: '添加路径', 45 | addshapes: '添加形状', 46 | circle: '圆', 47 | rect: '矩形', 48 | roundrect: '圆角矩形', 49 | star: '五角星', 50 | arrow: '箭头', 51 | triangle: '三角形', 52 | heart: '心形', 53 | tel: '电话', 54 | drop: '水滴', 55 | du: '度图标', 56 | setting: '设置', 57 | gridsorption: '吸附到网格线', 58 | shapesorption: '吸附到轮廓', 59 | showgrid: '显示网格', 60 | moresetting: '更多..', 61 | addreferenceline: '添加参考线', 62 | clearreferenceline: '清除参考线', 63 | addboundreferenceline: '添加边界参考线', 64 | rescale: '重置缩放', 65 | fontsetting: '字形信息', 66 | addpoint: '添加点', 67 | removepoint: '删除点', 68 | oncurve: '在曲线上', 69 | offcurve: '远离曲线', 70 | asstart: '作为开始点' 71 | }; 72 | -------------------------------------------------------------------------------- /src/editor/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 编辑器主函数 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import render from 'render/main'; 7 | import Editor from './Editor'; 8 | import defaultOptions from './options'; 9 | 10 | export default { 11 | 12 | /** 13 | * 创建一个编辑器 14 | * 15 | * @param {HTMLElement} main 主控区域 16 | * @param {Object} options 创建参数 17 | * @param {Object} options.controller 控制器函数 18 | * 19 | * @return {Render} render对象 20 | */ 21 | create(main, options) { 22 | 23 | if (!main) { 24 | throw 'need main element'; 25 | } 26 | 27 | options = Object.assign({}, defaultOptions, options); 28 | 29 | let editor = new Editor(main, options.editor); 30 | let opt = options.render || {}; 31 | 32 | opt.controller = editor; 33 | render.create(main, opt); 34 | return editor; 35 | } 36 | }; 37 | 38 | -------------------------------------------------------------------------------- /src/editor/menu/commandList.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 命令集合 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import point from './point'; 7 | import shape from './shape'; 8 | import shapes from './shapes'; 9 | import editor from './editor'; 10 | 11 | export default { 12 | point, 13 | shape, 14 | shapes, 15 | editor 16 | }; 17 | -------------------------------------------------------------------------------- /src/editor/menu/point.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 点相关命令 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | import i18n from '../i18n/i18n'; 6 | export default [ 7 | { 8 | name: 'add', 9 | title: i18n.lang.addpoint 10 | }, 11 | { 12 | name: 'remove', 13 | title: i18n.lang.removepoint 14 | }, 15 | { 16 | name: 'onCurve', 17 | title: i18n.lang.oncurve 18 | }, 19 | { 20 | name: 'offCurve', 21 | title: i18n.lang.offcurve 22 | }, 23 | { 24 | name: 'asStart', 25 | title: i18n.lang.asstart 26 | } 27 | ]; 28 | -------------------------------------------------------------------------------- /src/editor/mode/bound.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 默认模式 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import selectShape from 'render/util/selectShape'; 7 | import referenceline from './referenceline'; 8 | export default { 9 | 10 | 11 | down(e) { 12 | if (1 === e.which) { 13 | 14 | // 是否在边界拉出参考线 15 | if (e.x <= 20 || e.y <= 20) { 16 | this.setMode('referenceline', referenceline.newLine, e.x, e.y); 17 | return; 18 | } 19 | 20 | // 字体模式 21 | let result = this.fontLayer.getShapeIn(e); 22 | if (result) { 23 | let shape = selectShape(result, e); 24 | this.setMode('shapes', [shape], 'bound'); 25 | return; 26 | } 27 | 28 | // 参考线模式 29 | result = this.referenceLineLayer.getShapeIn(e); 30 | if (result) { 31 | let line = result[0]; 32 | this.setMode('referenceline', referenceline.dragLine, line, e); 33 | return; 34 | } 35 | 36 | this.setMode('range'); 37 | } 38 | }, 39 | 40 | 41 | keydown(e) { 42 | if (e.keyCode === 32) { 43 | this.setMode('pan'); 44 | } 45 | else if (e.keyCode === 65 && e.ctrlKey) { 46 | this.setMode('shapes', this.fontLayer.shapes.slice()); 47 | } 48 | } 49 | }; 50 | 51 | -------------------------------------------------------------------------------- /src/editor/mode/pan.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 区域查看模式 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | export default { 7 | 8 | drag(e) { 9 | if (1 === e.which) { 10 | let camera = this.render.camera; 11 | this.render.move(camera.mx, camera.my); 12 | this.render.refresh(); 13 | } 14 | }, 15 | 16 | keyup(e) { 17 | if (e.keyCode === 32) { 18 | this.setMode('bound'); 19 | } 20 | }, 21 | 22 | 23 | begin() { 24 | this.render.setCursor('pointer'); 25 | }, 26 | 27 | 28 | end() { 29 | this.render.setCursor('default'); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /src/editor/mode/split.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 轮廓切割模式 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | export default { 7 | 8 | down(e) { 9 | if (1 === e.which) { 10 | this.coverLayer.clearShapes(); 11 | this.splitLine = this.coverLayer.addShape({ 12 | type: 'line', 13 | p0: { 14 | x: e.x, 15 | y: e.y 16 | }, 17 | p1: { 18 | x: e.x, 19 | y: e.y 20 | } 21 | }); 22 | } 23 | }, 24 | 25 | 26 | move(e) { 27 | if (1 === e.which) { 28 | if (this.splitLine) { 29 | this.splitLine.p1.x = e.x; 30 | this.splitLine.p1.y = e.y; 31 | if (e.shiftKey) { 32 | this.splitLine.p1.y = this.splitLine.p0.y; 33 | } 34 | 35 | if (e.altKey) { 36 | this.splitLine.p1.x = this.splitLine.p0.x; 37 | } 38 | 39 | this.coverLayer.refresh(); 40 | } 41 | } 42 | }, 43 | 44 | 45 | up(e) { 46 | if (1 === e.which) { 47 | if (this.splitLine) { 48 | let p0 = this.splitLine.p0; 49 | let p1 = this.splitLine.p1; 50 | // 对shape进行多选 51 | if (Math.abs(p0.x - p1.x) >= 20 || Math.abs(p0.y - p1.y) >= 20) { 52 | if (false !== this.execCommand('splitshapes', p0, p1)) { 53 | return; 54 | } 55 | } 56 | } 57 | 58 | this.setMode(); 59 | } 60 | }, 61 | 62 | begin() { 63 | }, 64 | 65 | end() { 66 | delete this.splitLine; 67 | this.coverLayer.clearShapes(); 68 | this.coverLayer.refresh(); 69 | } 70 | }; 71 | 72 | -------------------------------------------------------------------------------- /src/editor/mode/support.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 编辑器支持的模式集合 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import point from './point'; 7 | import range from './range'; 8 | import pan from './pan'; 9 | import bound from './bound'; 10 | import shapes from './shapes'; 11 | import addshapes from './addshapes'; 12 | import addpath from './addpath'; 13 | import split from './split'; 14 | import referenceline from './referenceline'; 15 | 16 | 17 | export default { 18 | point, 19 | range, 20 | pan, 21 | 'default': bound, 22 | shapes, 23 | addshapes, 24 | addpath, 25 | split, 26 | referenceline 27 | }; 28 | -------------------------------------------------------------------------------- /src/editor/shapes/arrow.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export default [[{"x":1,"y":380,"onCurve":true},{"x":1,"y":423},{"x":43,"y":453},{"x":88,"y":453,"onCurve":true},{"x":109,"y":453},{"x":197,"y":452},{"x":296,"y":452},{"x":390,"y":451},{"x":418,"y":451,"onCurve":true},{"x":457,"y":450},{"x":502,"y":465},{"x":502,"y":493,"onCurve":true},{"x":502,"y":513},{"x":505,"y":570},{"x":505,"y":590,"onCurve":true},{"x":506,"y":626},{"x":545,"y":646},{"x":575,"y":621,"onCurve":true},{"x":605,"y":596},{"x":687,"y":529},{"x":775,"y":458},{"x":859,"y":390},{"x":892,"y":364,"onCurve":true},{"x":922,"y":339},{"x":923,"y":295},{"x":894,"y":272,"onCurve":true},{"x":864,"y":247},{"x":783,"y":184},{"x":696,"y":116},{"x":612,"y":50},{"x":580,"y":24,"onCurve":true},{"x":544,"y":-5},{"x":498,"y":10},{"x":499,"y":47,"onCurve":true},{"x":499,"y":71,"onCurve":true},{"x":499,"y":101,"onCurve":true},{"x":499,"y":116},{"x":498,"y":146},{"x":498,"y":158,"onCurve":true},{"x":498,"y":184},{"x":467,"y":197},{"x":440,"y":197,"onCurve":true},{"x":411,"y":197},{"x":314,"y":198},{"x":210,"y":198},{"x":112,"y":199},{"x":81,"y":199,"onCurve":true},{"x":68,"y":199},{"x":39,"y":204},{"x":15,"y":219},{"x":1,"y":246},{"x":1,"y":270,"onCurve":true},{"x":1,"y":297},{"x":0,"y":349}]]; 3 | -------------------------------------------------------------------------------- /src/editor/shapes/circle.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export default [[{"x":383,"y":0},{"x":207,"y":75},{"x":75,"y":208},{"x":0,"y":384},{"x":0,"y":583},{"x":75,"y":760},{"x":207,"y":891},{"x":383,"y":966},{"x":582,"y":966},{"x":758,"y":891},{"x":890,"y":760},{"x":965,"y":583},{"x":965,"y":384},{"x":890,"y":208},{"x":758,"y":75},{"x":582,"y":0}]]; 3 | -------------------------------------------------------------------------------- /src/editor/shapes/drop.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export default [[{"x":731,"y":526},{"x":674,"y":443,"onCurve":true},{"x":669,"y":437},{"x":589,"y":332},{"x":525,"y":242},{"x":446,"y":102},{"x":426,"y":41,"onCurve":true},{"x":421,"y":22},{"x":384,"y":0},{"x":347,"y":0},{"x":311,"y":22},{"x":305,"y":41,"onCurve":true},{"x":285,"y":102},{"x":206,"y":242},{"x":142,"y":332},{"x":62,"y":437},{"x":57,"y":443,"onCurve":true},{"x":0,"y":528},{"x":0,"y":621,"onCurve":true},{"x":0,"y":759},{"x":214,"y":953},{"x":517,"y":953},{"x":731,"y":759},{"x":731,"y":621,"onCurve":true}]]; 3 | -------------------------------------------------------------------------------- /src/editor/shapes/heart.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export default [[{"x":578,"y":965},{"x":546,"y":965},{"x":535,"y":953,"onCurve":true},{"x":143,"y":576,"onCurve":true},{"x":137,"y":570},{"x":116,"y":549},{"x":68,"y":488},{"x":29,"y":425},{"x":0,"y":337},{"x":0,"y":295,"onCurve":true},{"x":0,"y":156},{"x":159,"y":0},{"x":300,"y":0,"onCurve":true},{"x":340,"y":0},{"x":419,"y":27},{"x":490,"y":74},{"x":539,"y":114},{"x":562,"y":137,"onCurve":true},{"x":585,"y":114},{"x":634,"y":74},{"x":705,"y":27},{"x":786,"y":0},{"x":824,"y":0,"onCurve":true},{"x":965,"y":0},{"x":1124,"y":156},{"x":1124,"y":295,"onCurve":true},{"x":1124,"y":433},{"x":981,"y":576,"onCurve":true},{"x":589,"y":953,"onCurve":true}]]; 3 | -------------------------------------------------------------------------------- /src/editor/shapes/rect.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export default [[{"x":0,"y":0,"onCurve":true},{"x":200,"y":0,"onCurve":true},{"x":200,"y":200,"onCurve":true},{"x":0,"y":200,"onCurve":true}]]; 3 | -------------------------------------------------------------------------------- /src/editor/shapes/roundrect.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export default [[{"x":615,"y":573,"onCurve":true},{"x":615,"y":591},{"x":591,"y":615},{"x":573,"y":615,"onCurve":true},{"x":42,"y":615,"onCurve":true},{"x":24,"y":615},{"x":14,"y":601,"onCurve":true},{"x":0,"y":591},{"x":0,"y":573,"onCurve":true},{"x":0,"y":42,"onCurve":true},{"x":0,"y":24},{"x":14,"y":14,"onCurve":true},{"x":24,"y":0},{"x":42,"y":0,"onCurve":true},{"x":573,"y":0,"onCurve":true},{"x":591,"y":0},{"x":601,"y":14,"onCurve":true},{"x":615,"y":24},{"x":615,"y":42,"onCurve":true},{"x":615,"y":573,"onCurve":true}]]; 3 | -------------------------------------------------------------------------------- /src/editor/shapes/star.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export default [[{"x":756,"y":610,"onCurve":true},{"x":1009,"y":364,"onCurve":true},{"x":660,"y":315,"onCurve":true},{"x":504,"y":0,"onCurve":true},{"x":348,"y":315,"onCurve":true},{"x":0,"y":364,"onCurve":true},{"x":252,"y":610,"onCurve":true},{"x":193,"y":958,"onCurve":true},{"x":504,"y":793,"onCurve":true},{"x":815,"y":958,"onCurve":true}]]; 3 | -------------------------------------------------------------------------------- /src/editor/shapes/support.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 支持的自选形状 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import circle from './circle'; 7 | import rect from './rect'; 8 | import roundrect from './roundrect'; 9 | import arrow from './arrow'; 10 | import star from './star'; 11 | import triangle from './triangle'; 12 | import heart from './heart'; 13 | import tel from './tel'; 14 | import du from './du'; 15 | import drop from './drop'; 16 | 17 | export default { 18 | circle, 19 | rect, 20 | roundrect, 21 | arrow, 22 | star, 23 | triangle, 24 | heart, 25 | tel, 26 | du, 27 | drop 28 | }; 29 | -------------------------------------------------------------------------------- /src/editor/shapes/tel.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export default [[{"x":91,"y":42,"onCurve":true},{"x":116,"y":26},{"x":154,"y":14,"onCurve":true},{"x":191,"y":0},{"x":200,"y":0,"onCurve":true},{"x":209,"y":7},{"x":225,"y":56,"onCurve":true},{"x":244,"y":102},{"x":258,"y":139,"onCurve":true},{"x":279,"y":193},{"x":323,"y":269},{"x":318,"y":285},{"x":297,"y":309,"onCurve":true},{"x":277,"y":332},{"x":232,"y":362},{"x":230,"y":385,"onCurve":true},{"x":223,"y":413},{"x":258,"y":480,"onCurve":true},{"x":293,"y":545},{"x":355,"y":610,"onCurve":true},{"x":423,"y":668},{"x":478,"y":694,"onCurve":true},{"x":536,"y":717},{"x":569,"y":719,"onCurve":true},{"x":580,"y":719},{"x":634,"y":663},{"x":655,"y":636,"onCurve":true},{"x":657,"y":631},{"x":692,"y":647,"onCurve":true},{"x":722,"y":666},{"x":782,"y":701,"onCurve":true},{"x":843,"y":738},{"x":887,"y":768,"onCurve":true},{"x":928,"y":796},{"x":933,"y":803,"onCurve":true},{"x":931,"y":835},{"x":882,"y":877,"onCurve":true},{"x":831,"y":921},{"x":780,"y":942,"onCurve":true},{"x":736,"y":960},{"x":634,"y":949},{"x":546,"y":916,"onCurve":true},{"x":441,"y":882},{"x":121,"y":612},{"x":45,"y":422,"onCurve":true},{"x":-11,"y":258},{"x":3,"y":186,"onCurve":true},{"x":14,"y":111},{"x":45,"y":84,"onCurve":true},{"x":56,"y":67},{"x":68,"y":58,"onCurve":true},{"x":77,"y":46}]]; 3 | -------------------------------------------------------------------------------- /src/editor/shapes/triangle.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable*/ 2 | export default [[{"x":25,"y":871,"onCurve":true},{"x":431,"y":465,"onCurve":true},{"x":442,"y":454},{"x":442,"y":425},{"x":431,"y":414,"onCurve":true},{"x":25,"y":8,"onCurve":true},{"x":14,"y":-3},{"x":0,"y":4},{"x":0,"y":19,"onCurve":true},{"x":0,"y":860,"onCurve":true},{"x":0,"y":875},{"x":14,"y":882}]]; 3 | -------------------------------------------------------------------------------- /src/editor/util/cursor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 光标集合 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | export default { 7 | scale: { 8 | 1: 'nw-resize', 9 | 2: 'ne-resize', 10 | 3: 'se-resize', 11 | 4: 'sw-resize', 12 | 5: 's-resize', 13 | 6: 'e-resize', 14 | 7: 'n-resize', 15 | 8: 'w-resize' 16 | }, 17 | rotate: { 18 | 1: 'pointer', 19 | 2: 'pointer', 20 | 3: 'pointer', 21 | 4: 'pointer', 22 | 5: 'e-resize', 23 | 6: 's-resize', 24 | 7: 'w-resize', 25 | 8: 'n-resize' 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /src/editor/util/getFontHash.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 获取font的hashcode 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | /** 7 | * 获取font的hashcode用于比较font是否被编辑过 8 | * 9 | * @param {Object} font font结构 10 | * @param {Array} font.contours 轮廓数组 11 | * @param {Array} font.unicode unicode编码点 12 | * @param {number} font.advanceWidth 推荐宽度 13 | * @param {number} font.xMin xMin 14 | * @param {number} font.xMax xMax 15 | * @param {number} font.yMin yMin 16 | * @param {number} font.yMax yMax 17 | * 18 | * @return {number} hashcode 19 | */ 20 | export default function getFontHash(font) { 21 | 22 | let splice = Array.prototype.splice; 23 | let sequence = [ 24 | font.advanceWidth || 0 25 | ]; 26 | 27 | // contours 28 | if (font.contours && font.contours.length) { 29 | font.contours.forEach(function (contour) { 30 | contour.forEach(function (p) { 31 | sequence.push(p.x); 32 | sequence.push(p.y); 33 | sequence.push(p.onCurve ? 1 : 0); 34 | }); 35 | }); 36 | } 37 | 38 | if (font.unicode && font.unicode.length) { 39 | splice.apply(sequence, [sequence.length, 0].concat(font.unicode)); 40 | } 41 | 42 | if (font.name && font.name.length) { 43 | splice.apply( 44 | sequence, 45 | [sequence.length, 0].concat(font.name.split('').map(function (c) { 46 | return c.charCodeAt(0); 47 | })) 48 | ); 49 | } 50 | 51 | // 使用BKDR算法计算哈希 52 | // http://www.cnblogs.com/uvsjoh/archive/2012/03/27/2420120.html 53 | let hash = 0; 54 | for (let i = 0, l = sequence.length; i < l; i++) { 55 | hash = 0x7FFFFFFF & (hash * 131 + sequence[i]); 56 | } 57 | 58 | return hash; 59 | } 60 | -------------------------------------------------------------------------------- /src/editor/widget/GraduationMarker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 刻度指示 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | export default class GraduationMarker { 7 | 8 | /** 9 | * 刻度指示 10 | * 11 | * @constructor 12 | * @param {HTMLElement} main 主元素 13 | * @param {Object} options 选项参数 14 | */ 15 | constructor(main, options) { 16 | options = options || {}; 17 | let xAxis = document.createElement('div'); 18 | xAxis.className = 'marker-x'; 19 | let yAxis = document.createElement('div'); 20 | yAxis.className = 'marker-y'; 21 | 22 | if (options.thickness) { 23 | xAxis.style.width = options.thickness + 'px'; 24 | yAxis.style.height = options.thickness + 'px'; 25 | } 26 | 27 | main.appendChild(this.xAxis = xAxis); 28 | main.appendChild(this.yAxis = yAxis); 29 | } 30 | 31 | /** 32 | * 显示坐标 33 | * 34 | * @param {number} x x坐标 35 | * @param {number} y y坐标 36 | */ 37 | moveTo(x, y) { 38 | this.xAxis.style.top = y + 'px'; 39 | this.yAxis.style.left = x + 'px'; 40 | } 41 | 42 | /** 43 | * 注销 44 | */ 45 | dispose() { 46 | this.xAxis.parentNode.removeChild(this.xAxis); 47 | this.yAxis.parentNode.removeChild(this.yAxis); 48 | this.xAxis = this.yAxis = null; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/editor/widget/clipboard.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 剪切板 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | const storage = window.localStorage; 7 | const storageName = 'clipboard.default'; 8 | 9 | export default { 10 | 11 | /** 12 | * 设置clipboard 13 | * 14 | * @param {Object} data 设置的数据 15 | * @param {string} type 数据类型 16 | */ 17 | set(data, type) { 18 | storage.setItem(storageName, JSON.stringify({ 19 | type: type || -9999, 20 | data: data 21 | })); 22 | }, 23 | 24 | /** 25 | * 获取clipboard 26 | * 27 | * @param {string} type 数据类型 28 | * @return {?Object} 29 | */ 30 | get(type) { 31 | let data = storage.getItem(storageName); 32 | if (null !== data) { 33 | data = JSON.parse(data); 34 | if (data.type === type) { 35 | // storage.removeItem(storageName); 36 | return data.data; 37 | } 38 | } 39 | 40 | return null; 41 | }, 42 | 43 | /** 44 | * 清空 45 | */ 46 | clear() { 47 | storage.removeItem(storageName); 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /src/fonteditor/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 相关配置 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | export default { 7 | // 在线地址读取接口 8 | readOnline: location.hostname.indexOf('baidu.com') >= 0 9 | ? '/font/proxy?type=${0}&url=${1}' 10 | : './php/readOnline.php?type=${0}&file=${1}', 11 | 12 | // 用于form同步的代理页面地址 13 | proxyUrl: (function () { 14 | let a = document.createElement('a'); 15 | a.href = 'proxy.html'; 16 | return a.href; 17 | })() 18 | }; 19 | -------------------------------------------------------------------------------- /src/fonteditor/dialog/colorpicker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 颜色选择器 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | const defaultOptions = { 7 | customBG: '#222', 8 | margin: '5px -2px 0', 9 | doRender: 'div div', 10 | cssAddon: // could also be in a css file instead 11 | '.cp-color-picker{border:1px solid #999; padding:10px 10px 0;' 12 | + 'background:#eee; overflow:visible; border-radius:3px;}' 13 | + '.cp-color-picker:after{content:""; display:block; ' 14 | + 'position:absolute; top:-15px; left:12px; border:8px solid #eee;' 15 | + 'border-color: transparent transparent #eee}' 16 | // simulate border... 17 | + '.cp-color-picker:before{content:""; display:block; ' 18 | + 'position:absolute; top:-16px; left:12px; border:8px solid #eee;' 19 | + 'border-color: transparent transparent #999}' 20 | + '.cp-xy-slider:active {cursor:none;}' 21 | + '.cp-xy-slider{border:1px solid #999; margin-bottom:10px;}' 22 | + '.cp-xy-cursor{width:12px; height:12px; margin:-6px}' 23 | + '.cp-z-slider{margin-left:10px; border:1px solid #999;}' 24 | + '.cp-z-cursor{border-width:5px; margin-top:-5px;}' 25 | + '.cp-color-picker .cp-alpha{margin:10px 0 0; height:6px; border-radius:6px;' 26 | + 'overflow:visible; border:1px solid #999; box-sizing:border-box;' 27 | + 'background: linear-gradient(to right, rgba(238,238,238,1) 0%,rgba(238,238,238,0) 100%);}' 28 | + '.cp-color-picker .cp-alpha{margin:10px 0}' 29 | + '.cp-alpha-cursor{background: #eee; border-radius: 100%;' 30 | + 'width:14px; height:14px; margin:-5px -7px; border:1px solid #666!important;' 31 | + 'box-shadow:inset -2px -4px 3px #ccc}', 32 | renderCallback(e, toggle) { 33 | if (false === toggle) { 34 | this.$trigger.trigger('change'); 35 | } 36 | } 37 | }; 38 | 39 | 40 | export default { 41 | show(element, options) { 42 | let picker = $(element).colorPicker($.extend({}, defaultOptions, options)); 43 | return picker; 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /src/fonteditor/dialog/font-online.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 在线字体列表 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import string from 'common/string'; 7 | import i18n from '../i18n/i18n'; 8 | import setting from './setting'; 9 | import onlineList from '../data/online-font'; 10 | 11 | export default setting.derive({ 12 | 13 | title: i18n.lang.dialog_onlinefont, 14 | nofooter: true, 15 | 16 | getTpl() { 17 | 18 | let str = '
    '; 19 | onlineList.forEach(function (item, index) { 20 | str += '' 22 | + '' + string.encodeHTML(item.from) + '' 23 | + (index + 1) + '. ' 24 | + string.encodeHTML(item.name) 25 | + ''; 26 | }); 27 | 28 | str += '
    '; 29 | 30 | return str; 31 | }, 32 | 33 | set() { 34 | let me = this; 35 | let dialog = this.getDialog(); 36 | dialog.find('.list-group').on('click', '.list-group-item', function (e) { 37 | e.preventDefault(); 38 | e.stopPropagation(); 39 | let url = $(this).attr('data-url'); 40 | dialog.find('.list-group').off('click', '.list-group-item'); 41 | me.options.onChange && me.options.onChange.call(this, decodeURIComponent(url)); 42 | $('#model-dialog').modal('hide'); 43 | }); 44 | } 45 | }); 46 | -------------------------------------------------------------------------------- /src/fonteditor/dialog/font-url.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 读取线上字体 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import i18n from '../i18n/i18n'; 7 | import setting from './setting'; 8 | 9 | const tpl = '' 10 | + '
    ' 11 | + '' + i18n.lang.dialog_fonturl + '' 12 | + '' 13 | + '
    '; 14 | 15 | export default setting.derive({ 16 | 17 | title: i18n.lang.dialog_fonturl, 18 | 19 | getTpl() { 20 | return tpl; 21 | }, 22 | 23 | validate() { 24 | let setting = this.getFields(); 25 | if (setting.url) { 26 | return setting.url; 27 | } 28 | } 29 | }); 30 | 31 | -------------------------------------------------------------------------------- /src/fonteditor/dialog/setting-adjust-glyf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 设置调整字形 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import i18n from '../i18n/i18n'; 7 | import setting from './setting'; 8 | 9 | export default setting.derive({ 10 | 11 | title: i18n.lang.dialog_adjust_glyph, 12 | 13 | getTpl() { 14 | return require('../template/dialog/setting-adjust-glyf.tpl'); 15 | }, 16 | 17 | set(setting) { 18 | this.setFields(setting || {}); 19 | }, 20 | 21 | validate() { 22 | let setting = this.getFields(); 23 | 24 | if (setting.reverse === undefined 25 | && setting.mirror === undefined 26 | && setting.scale === undefined 27 | && setting.ajdustToEmBox === undefined 28 | ) { 29 | alert(i18n.lang.dialog_no_input); 30 | return false; 31 | } 32 | return setting; 33 | } 34 | 35 | }); 36 | -------------------------------------------------------------------------------- /src/fonteditor/dialog/setting-adjust-pos.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 设置自动调整字形位置 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | import i18n from '../i18n/i18n'; 6 | import setting from './setting'; 7 | 8 | export default setting.derive({ 9 | 10 | title: i18n.lang.dialog_adjust_pos, 11 | 12 | getTpl() { 13 | return require('../template/dialog/setting-adjust-pos.tpl'); 14 | }, 15 | 16 | set(setting) { 17 | this.setFields(setting || {}); 18 | }, 19 | 20 | validate() { 21 | let setting = this.getFields(); 22 | 23 | if (setting.leftSideBearing === undefined 24 | && setting.rightSideBearing === undefined 25 | && setting.verticalAlign === undefined 26 | ) { 27 | alert(i18n.lang.dialog_no_input); 28 | return false; 29 | } 30 | 31 | return setting; 32 | } 33 | 34 | }); 35 | 36 | -------------------------------------------------------------------------------- /src/fonteditor/dialog/setting-editor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 编辑器设置选项 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | 7 | import i18n from '../i18n/i18n'; 8 | import setting from './setting'; 9 | import lang from 'common/lang'; 10 | import program from '../widget/program'; 11 | 12 | export default setting.derive({ 13 | 14 | title: i18n.lang.dialog_editor_setting, 15 | 16 | getTpl() { 17 | return require('../template/dialog/setting-editor.tpl'); 18 | }, 19 | 20 | set(setting) { 21 | this.setting = lang.clone(setting); 22 | this.setFields(this.setting); 23 | let me = this; 24 | $('#setting-editor-default').on('click', function (e) { 25 | e.preventDefault(); 26 | me.setting = program.setting.getDefault('editor'); 27 | me.setFields(me.setting); 28 | me.oldSetting = null; 29 | }); 30 | }, 31 | onDispose() { 32 | $('#setting-editor-default').off('click'); 33 | }, 34 | validate() { 35 | return this.getFields(lang.clone(this.setting)); 36 | } 37 | 38 | }); 39 | -------------------------------------------------------------------------------- /src/fonteditor/dialog/setting-glyf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 设置自动调整字形位置 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import i18n from '../i18n/i18n'; 7 | import setting from './setting'; 8 | import stringUtil from 'fonteditor-core/ttf/util/string'; 9 | const unicodeREG = /^(?:\$[A-F0-9]+)(?:\,\$[A-F0-9]+)*$/gi; 10 | 11 | export default setting.derive({ 12 | 13 | title: i18n.lang.dialog_glyph_info, 14 | 15 | getTpl() { 16 | return require('../template/dialog/setting-glyf.tpl'); 17 | }, 18 | 19 | set(setting) { 20 | $('#setting-glyf-unicode').on('blur', function (e) { 21 | let val = $(this).val(); 22 | let ctlGlyfName = $('#setting-glyf-name'); 23 | if (!ctlGlyfName.val()) { 24 | if (val.match(unicodeREG)) { 25 | val = Number('0x' + val.split(',')[0].slice(1)); 26 | } 27 | else if (val) { 28 | val = val.charCodeAt(0); 29 | } 30 | ctlGlyfName.val(stringUtil.getUnicodeName(val)); 31 | } 32 | }); 33 | this.setFields(setting || {}); 34 | }, 35 | onDispose() { 36 | $('#setting-glyf-unicode').off('blur'); 37 | }, 38 | validate() { 39 | 40 | let setting = this.getFields(); 41 | 42 | if (setting.leftSideBearing === undefined 43 | && setting.rightSideBearing === undefined 44 | && setting.unicode === undefined 45 | && setting.name === undefined 46 | ) { 47 | alert(i18n.lang.dialog_no_input); 48 | return false; 49 | } 50 | 51 | return setting; 52 | } 53 | }); 54 | -------------------------------------------------------------------------------- /src/fonteditor/dialog/setting-ie.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 导入和导出设置选项 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import i18n from '../i18n/i18n'; 7 | import setting from './setting'; 8 | 9 | import tpl from '../template/dialog/setting-ie.tpl'; 10 | import lang from 'common/lang'; 11 | import program from '../widget/program'; 12 | 13 | export default setting.derive({ 14 | 15 | title: i18n.lang.dialog_import_and_export, 16 | 17 | getTpl() { 18 | return tpl; 19 | }, 20 | 21 | set(setting) { 22 | this.setting = lang.clone(setting); 23 | this.setFields(this.setting); 24 | let me = this; 25 | $('#setting-ie-default').on('click', function (e) { 26 | e.preventDefault(); 27 | me.setting = program.setting.getDefault('ie'); 28 | me.setFields(me.setting); 29 | me.oldSetting = null; 30 | }); 31 | }, 32 | onDispose() { 33 | $('#setting-ie-default').off('click'); 34 | }, 35 | validate() { 36 | let setting = this.getFields(lang.clone(this.setting)); 37 | return setting; 38 | } 39 | 40 | }); 41 | -------------------------------------------------------------------------------- /src/fonteditor/dialog/setting-name.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 设置字体命名信息 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import i18n from '../i18n/i18n'; 7 | import setting from './setting'; 8 | 9 | export default setting.derive({ 10 | 11 | title: i18n.lang.fontinfo, 12 | 13 | getTpl() { 14 | return require('../template/dialog/setting-name.tpl'); 15 | }, 16 | 17 | set(setting) { 18 | this.setFields(setting); 19 | }, 20 | 21 | validate() { 22 | let setting = this.getFields(); 23 | return setting; 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /src/fonteditor/dialog/setting-sync.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 同步字体设置 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import i18n from '../i18n/i18n'; 7 | import setting from './setting'; 8 | 9 | export default setting.derive({ 10 | 11 | title: i18n.lang.syncfont, 12 | 13 | getTpl() { 14 | return require('../template/dialog/setting-sync.tpl'); 15 | }, 16 | 17 | set(setting) { 18 | this.setFields(setting || {}); 19 | this.newProject = setting.newProject; 20 | let me = this; 21 | $('#setting-sync-cancel').on('click', function () { 22 | me.hide(0); 23 | }); 24 | }, 25 | 26 | onDispose() { 27 | $('#setting-sync-cancel').off('click'); 28 | }, 29 | 30 | validate() { 31 | let setting = this.getFields(); 32 | 33 | if (!setting.name) { 34 | alert(i18n.lang.dialog_alert_set_sync_name); 35 | return false; 36 | } 37 | 38 | if (!setting.url && !setting.pushUrl) { 39 | alert(i18n.lang.dialog_alert_set_url_or_syncurl); 40 | return false; 41 | } 42 | 43 | if (this.newProject && !setting.url) { 44 | alert(i18n.lang.dialog_alert_set_sync_url); 45 | return false; 46 | } 47 | 48 | if (!setting.woff && !setting.ttf && !setting.svg && !setting.eot) { 49 | alert(i18n.lang.dialog_alert_set_filetype); 50 | return false; 51 | } 52 | 53 | return setting; 54 | } 55 | }); 56 | -------------------------------------------------------------------------------- /src/fonteditor/dialog/setting-unicode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 设置代码点 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import i18n from '../i18n/i18n'; 7 | import setting from './setting'; 8 | 9 | const tpl = '' 10 | + '
    ' 11 | + '
    ' 12 | + '' 15 | + '' 20 | + '
    ' 21 | + '' 22 | + '
    ' 23 | + '
    ' 24 | + '
    ' 25 | + '${lang.dialog_generage_name}' 26 | + '' 27 | + '' 28 | + '' 29 | + '
    ' 30 | + '
    '; 31 | 32 | export default setting.derive({ 33 | 34 | title: i18n.lang.dialog_unicode_set, 35 | 36 | getTpl() { 37 | return tpl; 38 | }, 39 | 40 | validate() { 41 | let unicode = $('#setting-text-unicode').val(); 42 | if (unicode.match(/^\$[A-F0-9]+$/i)) { 43 | return (this.setting = { 44 | unicode: unicode, 45 | isGenerateName: $('#setting-text-unicode-name').is(':checked') 46 | }); 47 | } 48 | 49 | alert('代码点设置不正确'); 50 | return false; 51 | } 52 | }); 53 | -------------------------------------------------------------------------------- /src/fonteditor/dialog/support.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 支持的设置项目 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import unicode from '../dialog/setting-unicode'; 7 | import name from '../dialog/setting-name'; 8 | import adjustpos from '../dialog/setting-adjust-pos'; 9 | import adjustglyf from '../dialog/setting-adjust-glyf'; 10 | import metrics from '../dialog/setting-metrics'; 11 | import online from '../dialog/font-online'; 12 | import url from '../dialog/font-url'; 13 | import glyf from '../dialog/setting-glyf'; 14 | import editor from '../dialog/setting-editor'; 15 | import findglyf from '../dialog/setting-find-glyf'; 16 | import ie from '../dialog/setting-ie'; 17 | import importpic from '../dialog/setting-import-pic'; 18 | import sync from '../dialog/setting-sync'; 19 | import glyfdownload from '../dialog/glyf-download'; 20 | 21 | export default { 22 | unicode, 23 | name, 24 | 'adjust-pos': adjustpos, 25 | 'adjust-glyf': adjustglyf, 26 | metrics, 27 | online, 28 | url, 29 | glyf, 30 | editor, 31 | 'find-glyf': findglyf, 32 | ie, 33 | 'import-pic': importpic, 34 | sync, 35 | 'glyf-download': glyfdownload 36 | }; 37 | -------------------------------------------------------------------------------- /src/fonteditor/i18n/i18n.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 语言字符串管理 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | 7 | import I18n from 'common/I18n'; 8 | 9 | import zhcneditor from './zh-cn/editor'; 10 | import enuseditor from './en-us/editor'; 11 | 12 | import zhcnmessage from './zh-cn/message'; 13 | import enusmessage from './en-us/message'; 14 | 15 | import zhcndialog from './zh-cn/dialog'; 16 | import enusdialog from './en-us/dialog'; 17 | 18 | export default new I18n( 19 | [ 20 | ['zh-cn', zhcneditor], 21 | ['en-us', enuseditor], 22 | 23 | ['zh-cn', zhcnmessage], 24 | ['en-us', enusmessage], 25 | 26 | ['zh-cn', zhcndialog], 27 | ['en-us', enusdialog] 28 | ], 29 | window.language 30 | ); 31 | -------------------------------------------------------------------------------- /src/fonteditor/i18n/zh-cn/editor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file editor.js 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | export default { 7 | glyph_name: '命名', 8 | left_side_bearing: '左边距', 9 | right_side_bearing: '右边距', 10 | baseline_offset: '基线偏移', 11 | flip: '翻转', 12 | mirror: '镜像', 13 | 14 | scale: '按比例缩放', 15 | findglyf: '查找字形', 16 | downloadglyf: '导出字形', 17 | setunicode: '设置代码点', 18 | del: '删除', 19 | edit: '编辑', 20 | resume: '恢复', 21 | prevpage: '上一页', 22 | nextpage: '下一页', 23 | gotopage: '转到', 24 | 25 | saveas: '另存为', 26 | sync: '同步', 27 | syncfont: '同步字体', 28 | cancelsync: '取消同步', 29 | autosync: '自动同步', 30 | fonttype: '字体类型', 31 | remoteurl: '远程地址', 32 | fontname: '字体名称', 33 | fontinfo: '字体信息', 34 | ascent: '上升', 35 | descent: '下降', 36 | linegap: '行间距', 37 | usWinAscent: 'win上升', 38 | usWinDescent: 'win下降', 39 | calc: '计算', 40 | sTypoAscender: 'typo上升', 41 | sTypoDescender: 'typo下降', 42 | sTypoLineGap: 'typo间距', 43 | sxHeight: 'x高度', 44 | sCapHeight: '大写H高度', 45 | yStrikeoutPosition: '删除线位置', 46 | yStrikeoutSize: '删除线厚度', 47 | underlinePosition: '下划线位置', 48 | underlineThickness: '下划线厚度', 49 | ySubscriptXSize: '下标水平', 50 | ySubscriptYSize: '下标垂直', 51 | ySubscriptXOffset: '下标X偏移', 52 | ySubscriptYOffset: '下标Y偏移', 53 | ySuperscriptXSize: '上标水平', 54 | ySuperscriptYSize: '上标垂直', 55 | ySuperscriptXOffset: '上标X偏移', 56 | ySuperscriptYOffset: '上标Y偏移', 57 | achVendID: '供应商ID', 58 | usWeightClass: '粗细', 59 | usWidthClass: '宽度', 60 | panose: 'panose', 61 | 62 | fontFamily: '字体家族', 63 | fontSubFamily: '子字体家族', 64 | fullName: '完整字体名', 65 | uniqueSubFamily: '唯一字体识别名', 66 | version: '版本', 67 | postScriptName: 'PostScript名称', 68 | unitsPerEm: 'em框大小', 69 | lowestRecPPEM: '最小可读尺寸', 70 | created: '创建日期', 71 | modified: '修改日期', 72 | setting: '设置', 73 | help: '帮助', 74 | confirm: '确定', 75 | cancel: '取消', 76 | pushurl: '推送地址' 77 | }; 78 | -------------------------------------------------------------------------------- /src/fonteditor/i18n/zh-cn/message.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 消息提示 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | export default { 7 | msg_not_support_file_type: '不支持的文件类型!', 8 | msg_loading_pic: '正在加载图片...', 9 | msg_read_pic_error: '读取图片失败...', 10 | msg_processing: '正在处理...', 11 | msg_input_pic_url: '请输入图片URL!', 12 | msg_no_glyph_to_import: '没有找到可导入的字形!', 13 | msg_error_read_file: '读取文件出错!', 14 | msg_loading: '正在加载...', 15 | msg_confirm_del_proj: '是否删除项目?', 16 | msg_not_set_sync_info: '没有设置同步信息!', 17 | msg_no_sync_font: '没有要同步的字体!', 18 | msg_repeat_unicode: '重复的unicode代码点,字形序号:', 19 | msg_confirm_del_glyph: '确定删除字形么?', 20 | msg_read_file_error: '加载文件错误!', 21 | msg_syncing: '正在同步...', 22 | msg_sync_success: '同步成功...', 23 | msg_sync_failed: '同步失败:', 24 | msg_confirm_save_proj: '是否放弃保存当前编辑的项目?', 25 | msg_save_success: '保存成功...', 26 | msg_save_failed: '保存失败...', 27 | msg_input_proj_name: '请输入项目名称:', 28 | msg_confirm_gen_names: '生成的字形名称会覆盖原来的名称,确定生成?', 29 | msg_not_support_compound_glyf: '暂不支持复合字形!', 30 | msg_transform_compound_glyf: '是否转换复合字形为简单字形?', 31 | msg_confirm_save_glyph: '是否放弃保存当前编辑的字形?', 32 | msg_no_related_glhph: '未找到相关字形!', 33 | msg_error_open_proj: '打开项目失败,是否删除项目?', 34 | msg_error_del_proj: '删除项目失败,请刷新页面后删除!', 35 | msg_no_sort_glyf: '没有要排序的字形!', 36 | msg_has_compound_glyf_sort: '包含复合字形,无法进行排序', 37 | 38 | 39 | preview_title: '预览{%=fontFormat%}格式字体', 40 | preview_first_step: '第一步:使用font-face声明字体', 41 | preview_second_step: '第二步:定义使用{%=fontFamily%}的样式', 42 | preview_third_step: '第三步:挑选相应图标并获取字体编码,应用于页面', 43 | 44 | msg_error_sync_font: '无法获取同步字体!', 45 | msg_error_sync_font_address: '字体同步地址不可用!', 46 | msg_has_new_font_version: '字体`${fontName}`有新版本,是否同步新版本?', 47 | msg_error_sync_font_version: '同步新版本出错!' 48 | }; 49 | -------------------------------------------------------------------------------- /src/fonteditor/menu/viewer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file glyf列表相关命令 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import ei18n from 'editor/i18n/i18n'; 7 | import i18n from '../i18n/i18n'; 8 | export default [ 9 | { 10 | name: 'copy', 11 | title: ei18n.lang.copy, 12 | quickKey: 'C', 13 | disabled: true 14 | }, 15 | { 16 | name: 'cut', 17 | title: ei18n.lang.cut, 18 | quickKey: 'X' 19 | }, 20 | { 21 | name: 'paste', 22 | title: ei18n.lang.paste, 23 | quickKey: 'V' 24 | }, 25 | { 26 | name: 'del', 27 | title: ei18n.lang.del, 28 | quickKey: 'D', 29 | disabled: true 30 | }, 31 | { 32 | type: 'split' 33 | }, 34 | { 35 | name: 'adjust-pos', 36 | title: ei18n.lang.adjustpos 37 | }, 38 | { 39 | name: 'adjust-glyf', 40 | title: ei18n.lang.adjustglyf 41 | }, 42 | { 43 | name: 'setting-font', 44 | title: ei18n.lang.fontsetting, 45 | disabled: true 46 | }, 47 | { 48 | type: 'split' 49 | }, 50 | { 51 | name: 'find-glyf', 52 | title: i18n.lang.findglyf 53 | }, 54 | { 55 | name: 'download-glyf', 56 | title: i18n.lang.downloadglyf 57 | }, 58 | { 59 | type: 'split' 60 | }, 61 | { 62 | name: 'setting-unicode', 63 | title: i18n.lang.setunicode 64 | }, 65 | { 66 | name: 'setting-sync', 67 | title: i18n.lang.syncfont 68 | } 69 | ]; 70 | -------------------------------------------------------------------------------- /src/fonteditor/setting/editor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 默认的FontEditor设置 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import editorOptions from 'editor/options'; 7 | const editorDefault = editorOptions.editor; 8 | 9 | export default{ 10 | 11 | saveSetting: true, // 是否保存setting 12 | 13 | // 查看器选项 14 | viewer: { 15 | color: '', // 查看器颜色 16 | shapeSize: 'normal', // 字形大小 17 | pageSize: 100 // 翻页大小 18 | }, 19 | 20 | // 编辑器选项 21 | // see : editor/options.editor 22 | editor: { 23 | sorption: { 24 | enableGrid: false, 25 | enableShape: true 26 | }, 27 | fontLayer: { 28 | strokeColor: editorDefault.fontLayer.strokeColor, 29 | fill: true, 30 | fillColor: editorDefault.fontLayer.fillColor 31 | }, 32 | referenceline: { 33 | style: { 34 | strokeColor: editorDefault.referenceline.style.strokeColor 35 | } 36 | }, 37 | axis: { 38 | showGrid: true, 39 | gapColor: editorDefault.axis.gapColor, 40 | metricsColor: editorDefault.axis.metricsColor, 41 | graduation: { 42 | gap: editorDefault.axis.graduation.gap 43 | } 44 | } 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /src/fonteditor/setting/ie.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 导入和导出设置 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | export default { 7 | 8 | 'saveSetting': true, // 是否保存setting 9 | 10 | // 导入 11 | 'import': { 12 | combinePath: true // 导入svg文件时合并`path`标签 13 | }, 14 | 15 | // 导出 16 | 'export': { 17 | saveWithGlyfName: true // 导出字体时保存字形的名字 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /src/fonteditor/setting/support.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 支持的setting集合 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import editor from './editor'; 7 | import ie from './ie'; 8 | 9 | export default { 10 | editor, 11 | ie 12 | }; 13 | -------------------------------------------------------------------------------- /src/fonteditor/template/dialog/glyf-download.tpl: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | ${lang.dialog_glyf_name}: 4 |
    5 |
    6 | ${lang.dialog_color} 7 | 8 |
    9 |
    10 | 11 |
    12 |
    13 | ${lang.dialog_size} 14 | px 15 |
    16 |
    17 |
    18 |
    19 | 20 |
    21 |
    22 | 26 |
    27 |
    28 | -------------------------------------------------------------------------------- /src/fonteditor/template/dialog/setting-adjust-glyf.tpl: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | ${lang.flip} 4 | 5 | 6 | 7 |
    8 |
    9 |
    10 |
    11 | ${lang.mirror} 12 | 13 | 14 | 15 |
    16 |
    17 |
    18 |
    19 | ${lang.scale} 20 | 21 |
    22 |
    23 |
    24 |
    25 |
    26 | ${lang.dialog_scale_to_bound} 27 | 28 | 29 | 30 |
    31 |
    32 |
    33 |
    34 | ${lang.dialog_top_bottom_padding} 35 | 36 |
    37 |
    38 |
    39 | -------------------------------------------------------------------------------- /src/fonteditor/template/dialog/setting-adjust-pos.tpl: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | ${lang.left_side_bearing} 4 | 5 |
    6 |
    7 |
    8 |
    9 | ${lang.right_side_bearing} 10 | 11 |
    12 |
    13 |
    14 |
    15 | ${lang.baseline_offset} 16 | 17 |
    18 |
    19 | -------------------------------------------------------------------------------- /src/fonteditor/template/dialog/setting-glyf.tpl: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | unicode 4 | 5 |
    6 | ${lang.dialog_glyf_unicode_example} 7 |
    8 |
    9 |
    10 | ${lang.glyph_name} 11 | 12 |
    13 |
    14 |
    15 |
    16 | ${lang.left_side_bearing} 17 | 18 |
    19 |
    20 |
    21 |
    22 | ${lang.right_side_bearing} 23 | 24 |
    25 |
    26 | -------------------------------------------------------------------------------- /src/fonteditor/template/dialog/setting-ie.tpl: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | ${lang.dialog_combine_svg_single_glyph} 5 | 6 | 7 | 8 |
    9 |
    10 |
    11 | 12 |
    13 |
    14 |
    15 | ${lang.dialog_save_with_glyf_name} 16 | 17 | 18 | 19 |
    20 |
    21 |
    22 | 23 |
    24 |
    25 | ${lang.dialog_savesetting} 26 | 27 |
    28 | ${lang.dialog_resetsetting} 29 |
    30 | -------------------------------------------------------------------------------- /src/fonteditor/template/export-render.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file html渲染器 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import utpl from 'utpl'; 7 | import previewTpl from 'css/preview.lesstpl'; 8 | let fontExampleRender = null; // 图标示例渲染器 9 | let fontCssRender = null; // 图标css渲染器 10 | let symbolExampleRender = null; // symbol渲染器 11 | 12 | export default { 13 | 14 | /** 15 | * 渲染图标示例 16 | * 17 | * @param {Object} iconData 图标数据 18 | * @return {string} html片段 19 | */ 20 | renderFontExample(iconData) { 21 | fontExampleRender = fontExampleRender || utpl.template(require('./export/icon-example.tpl')); 22 | return fontExampleRender(iconData); 23 | }, 24 | 25 | /** 26 | * 渲染symbol图标示例 27 | * 28 | * @param {Object} iconData 图标数据 29 | * @return {string} html片段 30 | */ 31 | renderSymbolExample(iconData) { 32 | symbolExampleRender = symbolExampleRender || utpl.template(require('./export/symbol-example.tpl')); 33 | return symbolExampleRender(iconData); 34 | }, 35 | 36 | /** 37 | * 渲染图标css 38 | * 39 | * @param {Object} iconData 图标数据 40 | * @return {string} html片段 41 | */ 42 | renderFontCss(iconData) { 43 | fontCssRender = fontCssRender || utpl.template(require('./export/icon-css.tpl')); 44 | return fontCssRender(iconData); 45 | }, 46 | 47 | /** 48 | * 渲染预览css 49 | * 50 | * @return {string} html片段 51 | */ 52 | renderPreviewCss() { 53 | return previewTpl.toString(); 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /src/fonteditor/template/export/icon-css.tpl: -------------------------------------------------------------------------------- 1 | /** 2 | * @file icon.css 3 | */ 4 | 5 | @font-face { 6 | font-family: "{%=fontFamily%}"; 7 | src: url("{%=fontFamily%}.eot"); /* IE9 */ 8 | src: url("{%=fontFamily%}.eot?#iefix") format("embedded-opentype"), /* IE6-IE8 */ 9 | url("{%=fontFamily%}.woff2") format("woff2"), /* chrome、firefox、opera、Safari, Android, iOS */ 10 | url("{%=fontFamily%}.woff") format("woff"), /* chrome、firefox */ 11 | url("{%=fontFamily%}.ttf") format("truetype"), /* chrome、firefox、opera、Safari, Android, iOS 4.2+ */ 12 | url("{%=fontFamily%}.svg#uxfonteditor") format("svg"); /* iOS 4.1- */ 13 | } 14 | 15 | 16 | .{%=iconPrefix%} { 17 | font-family: "{%=fontFamily%}" !important; 18 | speak: none; 19 | font-style: normal; 20 | font-weight: normal; 21 | font-variant: normal; 22 | text-transform: none; 23 | line-height: 1; 24 | -webkit-font-smoothing: antialiased; 25 | -moz-osx-font-smoothing: grayscale; 26 | } 27 | 28 | {% _.each(glyfList, function(glyf) { %} 29 | .icon-{%=glyf.name%}:before { 30 | content: "{%=glyf.codeName%}"; 31 | } 32 | {% }); %} 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/fonteditor/template/export/icon-example.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | icon example 6 | 7 | 8 | 9 | 10 |
    11 |

    {%=fontFamily%} example

    12 | 21 |
    22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/fonteditor/template/export/symbol-example.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | symbol example 6 | 7 | 19 | 20 | 21 | 22 | {%=symbolText%} 23 |
    24 |

    {%=fontFamily%} symbol example

    25 | 34 |
    35 | 36 | 37 | -------------------------------------------------------------------------------- /src/fonteditor/template/preview-render.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 预览渲染器 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | 7 | import string from 'common/string'; 8 | import i18n from '../i18n/i18n'; 9 | import utpl from 'utpl'; 10 | import exportRender from './export-render'; 11 | let previewRender = null; // 预览渲染器 12 | 13 | export default { 14 | 15 | /** 16 | * 渲染预览页面 17 | * 18 | * @param {Object} data 预览页渲染数据 19 | * @param {Object} data.fontData 字体数据 20 | * @param {Object} data.fontFormat 字体格式 21 | * 22 | * @return {string} html片段 23 | */ 24 | renderPreview(data) { 25 | data.previewCss = exportRender.renderPreviewCss(); 26 | let tpl = string.format(require('./export/preview-ttf.tpl'), i18n); 27 | previewRender = previewRender || utpl.template(tpl); 28 | return previewRender(data); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /src/fonteditor/widget/loading.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 加载提示组件 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import i18n from '../i18n/i18n'; 7 | 8 | /** 9 | * 提示 10 | * 11 | * @type {Object} 12 | */ 13 | const loading = { 14 | 15 | /** 16 | * 显示提示框 17 | * 18 | * @param {string} text 显示文字 19 | * @param {number} duration 显示时间 20 | * @param {string} status 显示的状态 21 | * @return {this} 22 | */ 23 | show(text, duration, status) { 24 | 25 | clearTimeout(this.showtimer); 26 | let loading = $('#loading'); 27 | loading.attr('data-status', status || '') 28 | .find('span') 29 | .html(text || i18n.lang.msg_loading); 30 | loading.show(); 31 | 32 | if (duration) { 33 | this.showtimer = setTimeout(this.hide, duration); 34 | } 35 | 36 | return this; 37 | }, 38 | 39 | /** 40 | * 显示错误提示框 41 | * 42 | * @param {string} text 显示文字 43 | * @param {number} duration 显示时间 44 | * @return {this} 45 | */ 46 | error(text, duration) { 47 | this.show(text, duration, 'error'); 48 | return this; 49 | }, 50 | 51 | /** 52 | * 显示警告提示框 53 | * 54 | * @param {string} text 显示文字 55 | * @param {number} duration 显示时间 56 | * @return {this} 57 | */ 58 | warn(text, duration) { 59 | this.show(text, duration, 'warn'); 60 | return this; 61 | }, 62 | 63 | /** 64 | * 隐藏 65 | */ 66 | hide() { 67 | $('#loading').hide(); 68 | } 69 | }; 70 | 71 | export default loading; -------------------------------------------------------------------------------- /src/fonteditor/widget/sync-status.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 同步状态集合 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | export default { 7 | // 1 ~ 512 用来记录当前同步的状态,如是否支持拉取更新,是否支持推送 8 | serviceNotAvailable: 0x1, // 服务不可用 9 | pullNoResponse: 0x2, // 拉取不响应 10 | pushNoResponse: 0x4, // 推送不响应 11 | 12 | // 1000 以上,解析相关 13 | parseDataError: 1001, // 解析数据错误 14 | parseFontError: 1002, // 解析字体错误 15 | noHasNew: 1003, // 无更新记录 16 | cancelSync: 1004 // 取消更新记录 17 | }; 18 | -------------------------------------------------------------------------------- /src/fonteditor/widget/util/download.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file chrome下载 3 | * 由于浏览器限制,需要注意调用的时候需要在一次交互内,例如`click`等 4 | * 5 | * @author mengke01(kekee000@gmail.com) 6 | */ 7 | 8 | /** 9 | * chrome下载 10 | * 11 | * @param {string} fileName 文件名 12 | * @param {string} base64Str base64字符串 13 | */ 14 | export default function download(fileName, base64Str) { 15 | let a = document.createElement('a'); 16 | a.download = fileName; 17 | a.href = base64Str; 18 | a.addEventListener('click', function () { 19 | a.remove(); 20 | }); 21 | document.body.appendChild(a); 22 | if (a.click) { 23 | a.click(); 24 | } 25 | else { 26 | let event = document.createEvent('MouseEvents'); 27 | event.initMouseEvent('click', 28 | true, true, document.defaultView, 29 | 0, 0, 0, 0, 0, 30 | false, false, false, false, 31 | 0, null 32 | ); 33 | a.dispatchEvent(event); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/fonteditor/widget/util/resolvettf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 根据选项调整 ttfObject 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | 7 | import program from '../program'; 8 | 9 | /** 10 | * 根据选项调整 ttfObject,主要调整 post,head 等信息 11 | * 12 | * @param {Object} ttfObject ttf对象 13 | * @param {Object} options 参数选项 14 | * @return {Object} ttf对象 15 | */ 16 | export default function resolvettf(ttfObject, options = {}) { 17 | let exportSetting = program.setting.get('ie'); 18 | // 强制设置post表信息 19 | ttfObject.post = ttfObject.post || {}; 20 | if (exportSetting && exportSetting.export.saveWithGlyfName) { 21 | ttfObject.post.format = 2; 22 | } 23 | else { 24 | ttfObject.post.format = 3; 25 | } 26 | 27 | return ttfObject; 28 | } 29 | -------------------------------------------------------------------------------- /src/graphics/boundAdjust.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 对bound对象进行缩放和平移 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | /** 7 | * 对bound坐标进行调整 8 | * 9 | * @param {Object} bound bound数据结构 {x,y,width,height} 10 | * @param {number} scaleX x缩放比例 11 | * @param {number} scaleY y缩放比例 12 | * @param {number} offsetX x偏移 13 | * @param {number} offsetY y偏移 14 | * 15 | * @return {number} bound数据结构 16 | */ 17 | export default function boundAdjust(bound, scaleX, scaleY, offsetX, offsetY) { 18 | 19 | scaleX = scaleX === undefined ? 1 : scaleX; 20 | scaleY = scaleY === undefined ? 1 : scaleY; 21 | let x = offsetX || 0; 22 | let y = offsetY || 0; 23 | 24 | bound.x = scaleX * (bound.x + x); 25 | bound.y = scaleY * (bound.y + y); 26 | bound.width = scaleX * bound.width; 27 | bound.height = scaleY * bound.height; 28 | 29 | return bound; 30 | } 31 | -------------------------------------------------------------------------------- /src/graphics/computeBoundingBox.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 计算曲线包围盒 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import { 7 | 8 | computeBounding, 9 | quadraticBezier, 10 | computePath, 11 | computePathBox 12 | } from 'fonteditor-core/graphics/computeBoundingBox'; 13 | export default { 14 | computeBounding, 15 | quadraticBezier, 16 | computePath, 17 | computePathBox 18 | }; 19 | -------------------------------------------------------------------------------- /src/graphics/image/contour/douglasPeuckerReducePoints.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 消减非必要的点 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import vector from 'graphics/vector'; 7 | 8 | const THRESHOLD_DEFAULT = 1; // 默认消减点的阈值 9 | 10 | function reduce(contour, firstIndex, lastIndex, threshold, splitArray) { 11 | 12 | if (lastIndex - firstIndex < 3) { 13 | return; 14 | } 15 | 16 | let start = contour[firstIndex]; 17 | let end = contour[lastIndex]; 18 | let splitIndex = -1; 19 | let maxDistance = 0; 20 | for (let i = firstIndex + 1; i < lastIndex; i++) { 21 | let dist = vector.getDist(start, end, contour[i]); 22 | if (dist > maxDistance) { 23 | maxDistance = dist; 24 | splitIndex = i; 25 | } 26 | } 27 | 28 | if (maxDistance > threshold) { 29 | splitArray.push(splitIndex); 30 | reduce(contour, firstIndex, splitIndex, threshold, splitArray); 31 | reduce(contour, splitIndex, lastIndex, threshold, splitArray); 32 | } 33 | } 34 | 35 | 36 | /** 37 | * 消减非必要的点 38 | * 39 | * @param {Array} contour 轮廓点集 40 | * @param {number} firstIndex 起始索引 41 | * @param {number} lastIndex 结束索引 42 | * @param {number} scale 缩放级别 43 | * @param {number} threshold 消减阈值 44 | * @return {Array} 消减后的点集 45 | */ 46 | export default function reducePoint(contour, firstIndex = 0, lastIndex, scale, threshold) { 47 | lastIndex = lastIndex || contour.length - 1; 48 | threshold = threshold || THRESHOLD_DEFAULT * (scale || 1); 49 | let splitArray = []; 50 | 51 | reduce(contour, firstIndex, lastIndex, threshold, splitArray); 52 | 53 | if (splitArray.length) { 54 | splitArray.unshift(firstIndex); 55 | splitArray = splitArray.map(function (index) { 56 | contour[index].contourIndex = index; 57 | return contour[index]; 58 | }); 59 | 60 | splitArray.sort(function (a, b) { 61 | return a.contourIndex - b.contourIndex; 62 | }); 63 | return splitArray; 64 | } 65 | 66 | return contour; 67 | } 68 | -------------------------------------------------------------------------------- /src/graphics/image/contour/fitOval.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 拟合圆 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | 7 | import pathUtil from 'graphics/pathUtil'; 8 | import computeBoundingBox from 'graphics/computeBoundingBox'; 9 | import pathAdjust from 'graphics/pathAdjust'; 10 | import circlePath from 'graphics/path/circle'; 11 | import lang from 'common/lang'; 12 | 13 | /** 14 | * 拟合椭圆 15 | * 16 | * @param {Array} points 轮廓 17 | * @return {Object} bound 18 | */ 19 | export default function fitOval(points) { 20 | let b = computeBoundingBox.computeBounding(points); 21 | let bound = computeBoundingBox.computePath(circlePath); 22 | let scaleX = b.width / bound.width; 23 | let scaleY = b.height / bound.height; 24 | let contour = lang.clone(circlePath); 25 | pathAdjust(contour, scaleX, scaleY); 26 | pathAdjust(contour, 1, 1, b.x - bound.x, b.y - bound.y); 27 | return pathUtil.isClockWise(points) === -1 ? contour : contour.reverse(); 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/graphics/image/contour/smooth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 平滑轮廓 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | /** 7 | * 平滑图像轮廓 8 | * @param {Array} contour 轮廓点集 9 | * @param {number} smooth 平滑边界 10 | * @return {Array} 平滑后轮廓 11 | */ 12 | export default function smooth(contour, smooth) { 13 | 14 | smooth = Math.floor((smooth || 2) / 2); 15 | let div = smooth * 2 + 1; 16 | 17 | for (let i = 0, l = contour.length; i < l; i++) { 18 | let p = contour[i]; 19 | let xAvg = p.x; 20 | let yAvg = p.y; 21 | let index; 22 | for (let j = 0; j < smooth; j++) { 23 | index = (i + l - j) % l; 24 | xAvg += contour[index].x; 25 | yAvg += contour[index].y; 26 | 27 | index = (i + l + j) % l; 28 | xAvg += contour[index].x; 29 | yAvg += contour[index].y; 30 | } 31 | p.x = Math.floor(xAvg / div); 32 | p.y = Math.floor(yAvg / div); 33 | } 34 | 35 | return contour; 36 | } 37 | -------------------------------------------------------------------------------- /src/graphics/image/filter/binarize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 对图像进行二值化 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | /** 7 | * 二值化图像数据 8 | * 9 | * @param {Object} imageData 图像数据 10 | * @param {number} threshold 阈值 11 | * @return {Array} 二值化后的数据 12 | */ 13 | export default function binarize(imageData, threshold) { 14 | 15 | threshold = threshold || 200; 16 | let width = imageData.width; 17 | let height = imageData.height; 18 | let data = imageData.data; 19 | 20 | for (let y = 0, row = 0; y < height; y++) { 21 | row = y * width; 22 | for (let x = 0; x < width; x++) { 23 | data[row + x] = data[row + x] < threshold ? 0 : 255; 24 | } 25 | } 26 | 27 | imageData.binarize = true; 28 | 29 | return imageData; 30 | } 31 | 32 | -------------------------------------------------------------------------------- /src/graphics/image/filter/blur.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 图像模糊 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import filteringImage from '../util/filteringImage'; 7 | 8 | function getMatrix(radius) { 9 | let matrix = []; 10 | // 均值滤波 11 | let size = Math.pow(2 * radius + 1, 2); 12 | let value = 1 / size; 13 | 14 | for (let i = 0; i < size; i++) { 15 | matrix[i] = value; 16 | } 17 | 18 | return matrix; 19 | } 20 | 21 | /** 22 | * 灰度图像模糊 23 | * 24 | * @param {Object} imageData 图像数据 25 | * @param {number} radius 取样区域半径, 正数, 可选, 默认为 3 26 | * 27 | * @return {Object} 28 | */ 29 | export default function blur(imageData, radius) { 30 | 31 | radius = Math.floor((radius || 3) / 2); 32 | 33 | let data = imageData.data; 34 | let width = imageData.width; 35 | let height = imageData.height; 36 | let matrix = getMatrix(radius); 37 | 38 | imageData.data = filteringImage(data, width, height, radius, matrix); 39 | return imageData; 40 | } 41 | -------------------------------------------------------------------------------- /src/graphics/image/filter/brightness.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 亮度对比度计算 3 | * @author mengke01(kekee000@gmail.com) 4 | * 5 | * @reference 6 | * https://github.com/AlloyTeam/AlloyImage 7 | */ 8 | 9 | /** 10 | * 调节图像亮度对比度 11 | * 12 | * @param {Object} imageData 图像数据 13 | * @param {string} brightness 亮度 -50 ~ 50 14 | * @param {number} contrast 对比度 -50 ~ 50 15 | * @return {Object} 调整后图像 16 | */ 17 | export default function brightness(imageData, brightness, contrast) { 18 | 19 | brightness = brightness || 0; 20 | contrast = contrast || 0; 21 | 22 | let data = imageData.data; 23 | let b = brightness / 50; // -1 , 1 24 | let c = contrast / 50; // -1 , 1 25 | let k = Math.tan((45 + 44 * c) * Math.PI / 180); 26 | 27 | for (let i = 0, l = data.length; i < l; i++) { 28 | data[i] = Math.floor((data[i] - 127.5 * (1 - b)) * k + 127.5 * (1 + b)); 29 | } 30 | 31 | return imageData; 32 | } 33 | -------------------------------------------------------------------------------- /src/graphics/image/filter/close.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 二值图像开运算 3 | * 先膨胀后腐蚀的过程称为闭运算。用来填充物体内细小空洞、 4 | * 连接邻近物体、平滑其边界的同时并不明显改变其面积 5 | * @author mengke01(kekee000@gmail.com) 6 | */ 7 | 8 | import dilate from './dilate'; 9 | import erode from './erode'; 10 | 11 | export default function close(imageData, mode, radius) { 12 | return erode(dilate(imageData, mode, radius)); 13 | } 14 | -------------------------------------------------------------------------------- /src/graphics/image/filter/dilate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 对二值图像进行膨胀 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import {execute} from '../util/de'; 7 | 8 | export default function dilate(imageData, mode, radius) { 9 | return execute(imageData, 'dilate', mode, radius); 10 | } 11 | -------------------------------------------------------------------------------- /src/graphics/image/filter/erode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 对二值图像进行腐蚀 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import {execute} from '../util/de'; 7 | 8 | export default function erode(imageData, mode, radius) { 9 | return execute(imageData, 'erode', mode, radius); 10 | } 11 | -------------------------------------------------------------------------------- /src/graphics/image/filter/gaussBlur.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 高斯模糊 3 | * @author mengke01(kekee000@gmail.com) 4 | * 5 | * http://www.ruanyifeng.com/blog/2012/11/gaussian_blur.html 6 | * @reference 7 | * https://github.com/AlloyTeam/AlloyImage 8 | */ 9 | 10 | 11 | import filteringImage from '../util/filteringImage'; 12 | 13 | function getGaussMatrix(radius, sigma) { 14 | let gaussMatrix = []; 15 | let gaussSum = 0; 16 | let a = 1 / (2 * Math.PI * sigma * sigma); 17 | let b = -1 / (2 * sigma * sigma); 18 | let x; 19 | let y; 20 | let k; 21 | 22 | // 生成高斯矩阵 23 | for (k = 0, x = -radius; x <= radius; x++) { 24 | for (y = -radius; y <= radius; y++) { 25 | let g = a * Math.exp(b * (x * x + y * y)); 26 | gaussMatrix[k++] = g; 27 | gaussSum += g; 28 | } 29 | } 30 | 31 | // 归一化, 保证高斯矩阵的值在[0,1]之间 32 | for (k = 0, x = -radius; x <= radius; x++) { 33 | for (y = -radius; y <= radius; y++) { 34 | gaussMatrix[k++] /= gaussSum; 35 | } 36 | } 37 | 38 | return gaussMatrix; 39 | } 40 | 41 | /** 42 | * 灰度图像高斯模糊 43 | * 44 | * @param {Object} imageData 图像数据 45 | * @param {number} radius 取样区域半径, 正数, 可选, 默认为 3 46 | * @param {number} sigma 标准方差, 可选, 默认取值为 1.5 47 | * 48 | * @return {Object} 49 | */ 50 | export default function gaussBlur(imageData, radius, sigma) { 51 | 52 | radius = Math.floor((radius || 3) / 2); 53 | 54 | let data = imageData.data; 55 | let width = imageData.width; 56 | let height = imageData.height; 57 | let matrix = getGaussMatrix(radius, sigma || 1.5); 58 | 59 | imageData.data = filteringImage(data, width, height, radius, matrix); 60 | 61 | return imageData; 62 | } 63 | -------------------------------------------------------------------------------- /src/graphics/image/filter/gray.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 对图像进行灰度化处理 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | /** 7 | * 对图像进行灰度化处理 8 | * 9 | * @param {Object} imageData 图像数据 10 | * @param {boolean} reverse 是否反转图像 11 | * 12 | * @return {Object} 处理后的数据 13 | */ 14 | export default function grayImage(imageData, reverse) { 15 | 16 | let width = imageData.width; 17 | let height = imageData.height; 18 | let line = 0; 19 | let data = imageData.data; 20 | let newData = []; 21 | 22 | for (let y = 0; y < height; y++) { 23 | line = y * width; 24 | for (let x = 0; x < width; x++) { 25 | let idx = (x + line) * 4; 26 | let gray = 255; 27 | if (data[idx + 3] < 25) { 28 | gray = 255; 29 | } 30 | else { 31 | let r = data[idx + 0]; 32 | let g = data[idx + 1]; 33 | let b = data[idx + 2]; 34 | gray = Math.floor(0.299 * r + 0.587 * g + 0.114 * b); 35 | } 36 | newData[x + line] = reverse ? 255 - gray : gray; 37 | } 38 | } 39 | 40 | return { 41 | width: width, 42 | height: height, 43 | gray: true, 44 | data: newData 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /src/graphics/image/filter/open.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 二值图像开运算 3 | * 先腐蚀后膨胀的过程称为开运算。用来消除小物体、 4 | * 在纤细点处分离物体、平滑较大物体的边界的同时并不明显改变其面积 5 | * @author mengke01(kekee000@gmail.com) 6 | */ 7 | 8 | import dilate from './dilate'; 9 | import erode from './erode'; 10 | 11 | export default function (imageData, mode, radius) { 12 | return dilate(erode(imageData, mode, radius)); 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/graphics/image/filter/reverse.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 反转图像 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | /** 7 | * 调节图像亮度对比度 8 | * 9 | * @param {Object} imageData 图像数据 10 | * @return {Object} 调整后图像 11 | */ 12 | export default function reverse(imageData) { 13 | let data = imageData.data; 14 | for (let i = 0, l = data.length; i < l; i++) { 15 | data[i] = 255 - data[i]; 16 | } 17 | 18 | return imageData; 19 | } 20 | 21 | -------------------------------------------------------------------------------- /src/graphics/image/filter/sharp.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 图像锐化滤镜 3 | * @author mengke01(kekee000@gmail.com) 4 | * 5 | * @reference 6 | * http://dsqiu.iteye.com/blog/1638589 7 | * https://github.com/AlloyTeam/AlloyImage 8 | */ 9 | 10 | /** 11 | * 灰度图像锐化 12 | * @param {Object} imageData 图像数据 13 | * @param {number} lamta 锐化参数 14 | * @return {Object} 处理后图像 15 | */ 16 | export default function sharp(imageData, lamta = 0.6) { 17 | let width = imageData.width; 18 | let height = imageData.height; 19 | let data = imageData.data; 20 | for (let line = 0, y = 1; y < height; y++) { 21 | line = y * width; 22 | for (let x = 1; x < width; x++) { 23 | let idx = x + line; 24 | // 当前点减去 左侧三个像素点的平均值 25 | let delta = data[idx] - (data[idx - 1] + data[idx - width] + data[idx - width - 1]) / 3; 26 | data[idx] += Math.floor(delta * lamta); 27 | } 28 | } 29 | 30 | return imageData; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/graphics/image/util/cloneContours.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 克隆轮廓数组 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | /** 7 | * 克隆轮廓点集 8 | * 9 | * @param {Array} contours 轮廓点集 10 | * @return {Array} 克隆后的点集 11 | */ 12 | export default function cloneContours(contours) { 13 | let newContours = []; 14 | contours.forEach(function (contour) { 15 | let newContour = []; 16 | contour.forEach(function (p) { 17 | newContour.push({ 18 | x: p.x, 19 | y: p.y, 20 | onCurve: !!p.onCurve 21 | }); 22 | }); 23 | }); 24 | 25 | return newContours; 26 | } 27 | -------------------------------------------------------------------------------- /src/graphics/image/util/cloneImage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 克隆一个图像对象 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | 7 | export default function (imageData) { 8 | return { 9 | width: imageData.width, 10 | height: imageData.height, 11 | gray: imageData.gray, 12 | binarize: imageData.binarize, 13 | data: imageData.data.slice(0) 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /src/graphics/image/util/filteringImage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 图像滤波函数 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | /** 7 | * 滤波函数 8 | * 9 | * @param {Array} data 图像数据 10 | * @param {number} width 宽度 11 | * @param {number} height 高度 12 | * @param {number} radius 半径 13 | * @param {Array} matrix 滤波矩阵 14 | * 15 | * @return {Array} 滤波后数据 16 | */ 17 | export default function filteringImage(data, width, height, radius, matrix) { 18 | let x; 19 | let y; 20 | let i; 21 | let j; 22 | let k; 23 | let value; 24 | let posX; 25 | let posY; 26 | 27 | for (y = 0; y < height; y++) { 28 | for (x = 0; x < width; x++) { 29 | value = 0; 30 | for (k = 0, i = -radius; i <= radius; i++) { 31 | for (j = -radius; j <= radius; j++) { 32 | posX = x + i; 33 | 34 | if (posX < 0 || posX >= width) { 35 | posX = x - i; 36 | } 37 | 38 | posY = y + j; 39 | if (posY < 0 || posY >= height) { 40 | posY = y - j; 41 | } 42 | 43 | value += data[posX + posY * width] * matrix[k++]; 44 | } 45 | } 46 | 47 | data[x + y * width] = Math.floor(value); 48 | } 49 | } 50 | 51 | return data; 52 | } 53 | -------------------------------------------------------------------------------- /src/graphics/image/util/getHistogram.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 获取灰度图像的直方图信息 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | 7 | /** 8 | * 获取图像的灰度分布信息 9 | * 10 | * @param {Object} imageData 图像数据 11 | * @return {Array} 灰度统计信息 12 | */ 13 | export default function getHistogram(imageData) { 14 | let histogram = []; 15 | let i = 0; 16 | let l = 256; 17 | for (; i < l; i++) { 18 | histogram[i] = 0; 19 | } 20 | 21 | let data = imageData.data; 22 | for (i = 0, l = data.length; i < l; i++) { 23 | histogram[data[i]]++; 24 | } 25 | 26 | return histogram; 27 | } 28 | -------------------------------------------------------------------------------- /src/graphics/image/util/getThreshold.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 获取二值化阈值 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import thresholdTypes from './threshold'; 7 | 8 | 9 | /** 10 | * 获取图像的二值阈值 11 | * 12 | * @param {Array} histogram 灰度分布数组 13 | * @param {string} thresholdType 阈值类型 14 | * @return {number} 阈值 15 | */ 16 | export default function getThreshold(histogram, thresholdType) { 17 | let thrFunction = thresholdTypes[thresholdType] || thresholdTypes.mean; 18 | return thrFunction(histogram); 19 | } 20 | 21 | -------------------------------------------------------------------------------- /src/graphics/isBezierLineCross.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 判断贝塞尔曲线与直线相交 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import bezierQ2Equation from '../math/bezierQ2Equation'; 7 | import {ceilPoint} from './util'; 8 | 9 | /** 10 | * 判断贝塞尔曲线与直线相交 11 | * 12 | * @param {Object} p0 起点 13 | * @param {Object} p1 控制点 14 | * @param {Object} p2 终点 15 | * @param {Object} s0 直线点1 16 | * @param {Object} s1 直线点2 17 | * @return {Array.|boolean} 交点数组或者false 18 | */ 19 | export default function isBezierLineCross(p0, p1, p2, s0, s1) { 20 | 21 | // y = kx + b 22 | // x = at^2 + bt + c 23 | // y = dt^2 + et + f 24 | // (ka-d)t^2 + (kb-e)t + (kc+b-f) = 0 25 | let result; 26 | 27 | // 垂直x 28 | if (s0.y === s1.y) { 29 | result = bezierQ2Equation( 30 | p0.y + p2.y - 2 * p1.y, 31 | 2 * (p1.y - p0.y), 32 | p0.y - s0.y 33 | ); 34 | } 35 | // 垂直y 36 | else if (s0.x === s1.x) { 37 | result = bezierQ2Equation( 38 | p0.x + p2.x - 2 * p1.x, 39 | 2 * (p1.x - p0.x), 40 | p0.x - s0.x 41 | ); 42 | } 43 | else { 44 | 45 | let k = (s1.y - s0.y) / (s1.x - s0.x); 46 | let b1 = s0.y - k * s0.x; 47 | 48 | let a = p0.x + p2.x - 2 * p1.x; 49 | let b = 2 * (p1.x - p0.x); 50 | let c = p0.x; 51 | 52 | let d = p0.y + p2.y - 2 * p1.y; 53 | let e = 2 * (p1.y - p0.y); 54 | let f = p0.y; 55 | 56 | result = bezierQ2Equation( 57 | k * a - d, 58 | k * b - e, 59 | k * c + b1 - f 60 | ); 61 | } 62 | 63 | if (result) { 64 | return result.sort(function (t1, t2) { 65 | return t1 - t2; 66 | }).map(function (t) { 67 | return ceilPoint({ 68 | x: p0.x * Math.pow(1 - t, 2) + 2 * p1.x * t * (1 - t) + p2.x * Math.pow(t, 2), 69 | y: p0.y * Math.pow(1 - t, 2) + 2 * p1.y * t * (1 - t) + p2.y * Math.pow(t, 2) 70 | }); 71 | }); 72 | } 73 | 74 | return false; 75 | } 76 | -------------------------------------------------------------------------------- /src/graphics/isBezierRayCross.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 判断x轴射线是否与贝塞尔曲线相交 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import isBezierLineCross from './isBezierLineCross'; 7 | 8 | /** 9 | * 判断x轴射线是否与贝塞尔曲线相交 10 | * @param {Object} p0 起点 11 | * @param {Object} p1 控制点 12 | * @param {Object} p2 终点 13 | * @param {Object} p 射线起点 14 | * @return {Array|boolean} 交点数组或者false 15 | */ 16 | export default function isBezierRayCross(p0, p1, p2, p) { 17 | 18 | // 3点都在同一侧 19 | if (0 === ((p0.y > p.y) + (p1.y > p.y) + (p2.y > p.y)) % 3) { 20 | return false; 21 | } 22 | 23 | let result = isBezierLineCross(p0, p1, p2, p, {x: 100000, y: p.y}); 24 | if (result) { 25 | let filter = result.filter(function (item) { 26 | return item.x >= p.x; 27 | }); 28 | return filter.length ? filter : false; 29 | } 30 | 31 | return false; 32 | } 33 | -------------------------------------------------------------------------------- /src/graphics/isBezierSegmentCross.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 判断贝塞尔曲线与线段相交 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import computeBoundingBox from './computeBoundingBox'; 7 | import isBezierLineCross from './isBezierLineCross'; 8 | import isBoundingBoxCross from './isBoundingBoxCross'; 9 | import {isPointInBound} from './util'; 10 | 11 | /** 12 | * 判断贝塞尔曲线与线段相交 13 | * 14 | * @param {Object} p0 起点 15 | * @param {Object} p1 控制点 16 | * @param {Object} p2 终点 17 | * @param {Object} s0 线段点1 18 | * @param {Object} s1 线段点2 19 | * @return {Array.|boolean} 交点数组或者false 20 | */ 21 | export default function isBezierSegmentCross(p0, p1, p2, s0, s1) { 22 | let b1 = computeBoundingBox.quadraticBezier(p0, p1, p2); 23 | let bound = { 24 | x: Math.min(s0.x, s1.x), 25 | y: Math.min(s0.y, s1.y), 26 | width: Math.abs(s0.x - s1.x), 27 | height: Math.abs(s0.y - s1.y) 28 | }; 29 | 30 | if (isBoundingBoxCross(b1, bound)) { 31 | let result = isBezierLineCross(p0, p1, p2, s0, s1); 32 | if (result) { 33 | return result.filter(function (p) { 34 | return isPointInBound(bound, p, true); 35 | }); 36 | } 37 | } 38 | 39 | return false; 40 | } 41 | -------------------------------------------------------------------------------- /src/graphics/isBoundingBoxCross.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 求两个boundingbox的关系 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import {isPointInBound} from './util'; 7 | 8 | 9 | /** 10 | * 求两个boundingbox的关系 11 | * 12 | * @param {Object} b1 bounding 1 13 | * @param {Object} b2 bounding 2 14 | * @return {number} 包含关系 15 | * 16 | * 2 : b2 包含 b1 17 | * 3 : b1 包含 b2 18 | * 1 : 有交点 19 | */ 20 | export default function isBoundingBoxCross(b1, b2) { 21 | let b1lt = isPointInBound(b2, b1, true); // 左上 22 | let b1rt = isPointInBound(b2, {x: b1.x + b1.width, y: b1.y}, true); // 右上 23 | let b1lb = isPointInBound(b2, {x: b1.x, y: b1.y + b1.height}, true); // 左下 24 | let b1rb = isPointInBound(b2, {x: b1.x + b1.width, y: b1.y + b1.height}, true); // 右下 25 | 26 | // b2 包含 b1 27 | if (b1lt && b1rt && b1lb && b1rb) { 28 | return 2; 29 | } 30 | 31 | let b2lt = isPointInBound(b1, b2, true); // 左上 32 | let b2rt = isPointInBound(b1, {x: b2.x + b2.width, y: b2.y}, true); // 右上 33 | let b2lb = isPointInBound(b1, {x: b2.x, y: b2.y + b2.height}, true); // 左下 34 | let b2rb = isPointInBound(b1, {x: b2.x + b2.width, y: b2.y + b2.height}, true); // 右下 35 | 36 | // b1 包含 b2 37 | if (b2lt && b2rt && b2lb && b2rb) { 38 | return 3; 39 | } 40 | 41 | // 无交点 42 | if (false === (b1lt || b1rt || b1lb || b1rb || b2lt || b2rt || b2lb || b2rb)) { 43 | // 判断十字架 44 | if ( 45 | (b1.x > b2.x && b1.x < b2.x + b2.width) 46 | || (b1.y > b2.y && b1.y < b2.y + b2.height) 47 | ) { 48 | return 1; 49 | } 50 | 51 | return false; 52 | } 53 | 54 | // 有交点 55 | return 1; 56 | } 57 | -------------------------------------------------------------------------------- /src/graphics/isBoundingBoxSegmentCross.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 求boundingbox和线段的关系 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | 7 | import {isPointInBound} from './util'; 8 | import isSegmentCross from './isSegmentCross'; 9 | 10 | /** 11 | * 求boundingbox和线段的关系 12 | * 13 | * @param {Object} bound bounding box 14 | * @param {Object} s0 线段点1 15 | * @param {Object} s1 线段点2 16 | * @return {boolean} 是否相交 17 | */ 18 | export default function isBoundingBoxSegmentCross(bound, s0, s1) { 19 | if (isPointInBound(bound, s0) || isPointInBound(bound, s1)) { 20 | return true; 21 | } 22 | 23 | if ( 24 | isSegmentCross(bound, {x: bound.x, y: bound.y + bound.height}, s0, s1) 25 | || isSegmentCross(bound, {x: bound.x + bound.width, y: bound.y}, s0, s1) 26 | || isSegmentCross( 27 | {x: bound.x + bound.width, y: bound.y}, 28 | {x: bound.x + bound.width, y: bound.y + bound.height}, 29 | s0, s1) 30 | || isSegmentCross( 31 | {x: bound.x, y: bound.y + bound.height}, 32 | {x: bound.x + bound.width, y: bound.y + bound.height}, 33 | s0, s1) 34 | ) { 35 | return true; 36 | } 37 | 38 | return false; 39 | } 40 | -------------------------------------------------------------------------------- /src/graphics/isInsidePolygon.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 判断点是否在polygon内部 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import isSegmentRayCross from './isSegmentRayCross'; 7 | 8 | /** 9 | * 多边形包含判断, 射线法 10 | * 11 | * @param {Array.} points 多边形点 12 | * @param {Object} p 点 13 | * @return {boolean} 是否包含 14 | */ 15 | export default function isInsidePolygon(points, p) { 16 | 17 | let zCount = 0; 18 | let p0; 19 | let p1; 20 | let result; 21 | for (let i = 0, l = points.length; i < l; i++) { 22 | p0 = points[i]; 23 | p1 = points[i === l - 1 ? 0 : i + 1]; 24 | 25 | if ((result = isSegmentRayCross(p0, p1, p))) { 26 | // 在线段上 27 | if (result.y === p.y) { 28 | return true; 29 | } 30 | zCount += result.length; 31 | } 32 | } 33 | 34 | return !!(zCount % 2); 35 | } 36 | -------------------------------------------------------------------------------- /src/graphics/isOnPath.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 判断点是否在路径上 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import pathIterator from './pathIterator'; 7 | import getBezierQ2T from 'math/getBezierQ2T'; 8 | 9 | /** 10 | * 判断点是否在path上 11 | * 12 | * @param {Object} path path对象 13 | * @param {Object} p 点对象 14 | * @return {boolean|number} 是否在path上,如果在的话,返回起点索引号 15 | */ 16 | export default function isOnPath(path, p) { 17 | let zCount = false; 18 | pathIterator(path, function (c, p0, p1, p2, i) { 19 | if (c === 'L') { 20 | if (Math.abs((p.y - p0.y) * (p.x - p1.x) - (p.y - p1.y) * (p.x - p0.x)) <= 0.001) { 21 | zCount = i; 22 | return false; 23 | } 24 | } 25 | else if (c === 'Q') { 26 | if (false !== getBezierQ2T(p0, p1, p2, p)) { 27 | zCount = i; 28 | return false; 29 | } 30 | } 31 | }); 32 | 33 | return zCount; 34 | } 35 | -------------------------------------------------------------------------------- /src/graphics/isPathCross.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 判断路径的包含关系 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import computeBoundingBox from './computeBoundingBox'; 7 | import getPathJoint from './join/getPathJoint'; 8 | import isInsidePath from './isInsidePath'; 9 | import isBoundingBoxCross from './isBoundingBoxCross'; 10 | import util from './pathUtil'; 11 | 12 | /** 13 | * 判断路径的包含关系 14 | * 15 | * @param {Array} path0 路径0 16 | * @param {Array} path1 路径1 17 | * @param {Object=} bound0 第一个路径边界 18 | * @param {Object=} bound1 第二个路径边界 19 | * @return {Array|number} 交点数组或者包含关系 20 | * 21 | * 2: path0 包含 path1 22 | * 3: path1 包含 path0 23 | * 4: 重叠 24 | */ 25 | export default function isPathCross(path0, path1, bound0, bound1) { 26 | bound0 = bound0 || computeBoundingBox.computePath(path0); 27 | bound1 = bound1 || computeBoundingBox.computePath(path1); 28 | 29 | if (isBoundingBoxCross(bound0, bound1)) { 30 | let result = getPathJoint(path0, path1); 31 | if (!result) { 32 | // 0 包含 1 33 | if (isInsidePath(path0, path1[0])) { 34 | return 2; 35 | } 36 | // 1 包含 0 37 | else if (isInsidePath(path1, path0[0])) { 38 | return 3; 39 | } 40 | } 41 | 42 | return util.removeOverlapPoints(result); 43 | } 44 | 45 | return false; 46 | } 47 | -------------------------------------------------------------------------------- /src/graphics/isSegmentRayCross.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 判断x轴射线是否穿过线段 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import isSegmentCross from './isSegmentCross'; 7 | 8 | /** 9 | * 判断x轴射线是否穿过线段 10 | * 11 | * @param {Object} p0 线段p0 12 | * @param {Object} p1 线段p1 13 | * @param {Object} p 射线起点 14 | * @return {boolean} 是否 15 | */ 16 | export default function isSegmentRayCross(p0, p1, p) { 17 | return isSegmentCross(p0, p1, p, {x: 100000, y: p.y}); 18 | } 19 | -------------------------------------------------------------------------------- /src/graphics/join/getPathJoint.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 获取路径交点 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import getJoint from './getJoint'; 7 | import pathIterator from '../pathIterator'; 8 | 9 | /** 10 | * 求两个路径的交点集合 11 | * @param {Array} path0 路径0 12 | * @param {Array} path1 路径1 13 | * 14 | * @return {Array} 交点数组 15 | */ 16 | export default function getPathJoint(path0, path1) { 17 | 18 | let joint = []; 19 | let result; 20 | pathIterator(path0, function (c, p0, p1, p2, i) { 21 | if (c === 'L') { 22 | result = getJoint(path1, 'L', p0, p1, 0, i); 23 | } 24 | else if (c === 'Q') { 25 | result = getJoint(path1, 'Q', p0, p1, p2, i); 26 | } 27 | 28 | if (result) { 29 | joint = joint.concat(result); 30 | } 31 | }); 32 | 33 | return joint.length ? joint : false; 34 | } 35 | -------------------------------------------------------------------------------- /src/graphics/matrix.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 一维矩阵操作 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | /** 7 | * 矩阵乘系数 8 | * 9 | * @param {Array.} matrix 一维矩阵 10 | * @param {number} a 乘数算子 11 | * @return {Array.} 数组 12 | */ 13 | export function multi(matrix, a) { 14 | return matrix.map(function (item) { 15 | return item * a; 16 | }); 17 | } 18 | 19 | /** 20 | * 矩阵减 21 | * 22 | * @param {Array.} matrix1 一维矩阵 23 | * @param {Array.} matrix2 一维矩阵 24 | * @return {Array.} 数组 25 | */ 26 | export function minus(matrix1, matrix2) { 27 | return matrix1.map(function (item, index) { 28 | return item - matrix2[index]; 29 | }); 30 | } 31 | 32 | /** 33 | * 矩阵加 34 | * 35 | * @param {Array.} matrix1 一维矩阵 36 | * @param {Array.} matrix2 一维矩阵 37 | * @return {Array.} 数组 38 | */ 39 | export function plus(matrix1, matrix2) { 40 | return matrix1.map(function (item, index) { 41 | return item + matrix2[index]; 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /src/graphics/path/circle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 圆路径集合,逆时针 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | export {default} from 'fonteditor-core/graphics/path/circle'; 7 | -------------------------------------------------------------------------------- /src/graphics/pathAdjust.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 调整路径缩放和平移 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | export {default} from 'fonteditor-core/graphics/pathAdjust'; 7 | 8 | -------------------------------------------------------------------------------- /src/graphics/pathCeil.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 对路径进行四舍五入 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | export {default} from 'fonteditor-core/graphics/pathCeil'; 7 | -------------------------------------------------------------------------------- /src/graphics/pathIterator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 遍历路径的路径集合,包括segment和 bezier curve 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | export {default} from 'fonteditor-core/graphics/pathIterator'; 7 | 8 | -------------------------------------------------------------------------------- /src/graphics/pathRotate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 路径旋转 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | export {default} from 'fonteditor-core/graphics/pathRotate'; 7 | -------------------------------------------------------------------------------- /src/graphics/pathSkew.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file path倾斜变换 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | export {default} from 'fonteditor-core/graphics/pathSkew'; -------------------------------------------------------------------------------- /src/graphics/pathTransform.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 对轮廓进行transform变换 3 | * @author mengke01(kekee000@gmail.com) 4 | * 5 | * 参考资料: 6 | * http://blog.csdn.net/henren555/article/details/9699449 7 | * 8 | * |X| |a c e| |x| 9 | * |Y| = |b d f| * |y| 10 | * |1| |0 0 1| |1| 11 | * 12 | * X = x * a + y * c + e 13 | * Y = x * b + y * d + f 14 | */ 15 | 16 | export {default} from 'fonteditor-core/graphics/pathTransform'; 17 | -------------------------------------------------------------------------------- /src/graphics/pathUtil.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 路径相关的函数集合 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import { 7 | interpolate, 8 | deInterpolate, 9 | isClockWise, 10 | getPathHash, 11 | removeOverlapPoints, 12 | makeLink, 13 | scale, 14 | clone 15 | } from 'fonteditor-core/graphics/pathUtil'; 16 | 17 | export default { 18 | interpolate, 19 | deInterpolate, 20 | isClockWise, 21 | getPathHash, 22 | removeOverlapPoints, 23 | makeLink, 24 | scale, 25 | clone 26 | }; 27 | -------------------------------------------------------------------------------- /src/graphics/pathsUtil.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 路径组变化函数 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | export {default} from 'fonteditor-core/graphics/pathsUtil'; 7 | -------------------------------------------------------------------------------- /src/graphics/reducePath.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 缩减path大小,去除冗余节点 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | export {default} from 'fonteditor-core/graphics/reducePath'; 7 | -------------------------------------------------------------------------------- /src/graphics/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file grahpics点相关工具箱 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | export {ceilPoint, ceil, isPointInBound, isPointOverlap, getPointHash} from 'fonteditor-core/graphics/util'; 7 | -------------------------------------------------------------------------------- /src/math/bezierCubeEquation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 求解三次方程贝塞尔根 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import cubeEquation from './cubeEquation'; 7 | 8 | /** 9 | * 求解三次方程 10 | * 11 | * @param {number} a a系数 12 | * @param {number} b b系数 13 | * @param {number} c c系数 14 | * @param {number} d d系数 15 | * @return {Array|boolean} 系数解 16 | */ 17 | export default function bezierCubeEquation(a, b, c, d) { 18 | let result = cubeEquation(a, b, c, d); 19 | 20 | if (!result) { 21 | return result; 22 | } 23 | 24 | let filter = result.filter(function (item) { 25 | return item >= 0 && item <= 1; 26 | }); 27 | 28 | return filter.length 29 | ? filter 30 | : false; 31 | } 32 | -------------------------------------------------------------------------------- /src/math/bezierCubic2Q2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 三次贝塞尔转二次贝塞尔 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | export {default} from 'fonteditor-core/math/bezierCubic2Q2'; -------------------------------------------------------------------------------- /src/math/bezierQ2Equation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 求解二次方程贝塞尔根 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import quadraticEquation from './quadraticEquation'; 7 | 8 | /** 9 | * 求解二次方程贝塞尔根 10 | * 11 | * @param {number} a a系数 12 | * @param {number} b b系数 13 | * @param {number} c c系数 14 | * @return {Array|boolean} 系数解 15 | */ 16 | export default function bezierQ2Equation(a, b, c) { 17 | let result = quadraticEquation(a, b, c); 18 | 19 | if (!result) { 20 | return result; 21 | } 22 | 23 | let filter = result.filter(function (item) { 24 | return item >= 0 && item <= 1; 25 | }); 26 | 27 | return filter.length ? filter : false; 28 | } 29 | -------------------------------------------------------------------------------- /src/math/bezierQ2Split.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 分割二次贝塞尔曲线 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import getBezierQ2T from './getBezierQ2T'; 7 | import getPoint from './getBezierQ2Point'; 8 | 9 | /** 10 | * 分割贝塞尔曲线 11 | * 12 | * @param {Object} p0 p0 13 | * @param {Object} p1 p1 14 | * @param {Object} p2 p2 15 | * @param {number|Object} point 分割点t或者坐标 16 | * @return {Array} 分割后的贝塞尔 17 | */ 18 | export default function bezierQ2Split(p0, p1, p2, point) { 19 | let t; 20 | let p; 21 | 22 | if (typeof point === 'number') { 23 | t = point; 24 | p = getPoint(p0, p1, p2, t); 25 | } 26 | else if (typeof point === 'object') { 27 | p = point; 28 | t = getBezierQ2T(p0, p1, p2, p); 29 | 30 | if (false === t) { 31 | return false; 32 | } 33 | } 34 | 35 | if (t === 0 || t === 1) { 36 | return [[p0, p1, p2]]; 37 | } 38 | 39 | return [ 40 | [ 41 | p0, 42 | { 43 | x: p0.x + (p1.x - p0.x) * t, 44 | y: p0.y + (p1.y - p0.y) * t 45 | }, 46 | p 47 | ], 48 | [ 49 | p, 50 | { 51 | x: p1.x + (p2.x - p1.x) * t, 52 | y: p1.y + (p2.y - p1.y) * t 53 | }, 54 | p2 55 | ] 56 | ]; 57 | } 58 | -------------------------------------------------------------------------------- /src/math/bezierQ4Equation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 求解四次方程贝塞尔根 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import quarticEquation from './quarticEquation'; 7 | 8 | /** 9 | * 求解四次方程贝塞尔根 10 | * 11 | * @param {number} a a系数 12 | * @param {number} b b系数 13 | * @param {number} c c系数 14 | * @param {number} d d系数 15 | * @param {number} e e系数 16 | * @return {Array|boolean} 系数解 17 | */ 18 | export default function bezierQuarticEquation(a, b, c, d, e) { 19 | let result = quarticEquation(a, b, c, d, e); 20 | 21 | if (!result) { 22 | return result; 23 | } 24 | 25 | let filter = result.filter(function (item) { 26 | return item >= 0 && item <= 1; 27 | }); 28 | 29 | return filter.length 30 | ? filter 31 | : false; 32 | } 33 | -------------------------------------------------------------------------------- /src/math/getAngle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 获取向量夹角,带方向 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | /** 7 | * 获取向量夹角, 相对于坐标原点 8 | * 9 | * @param {number} x1 起始x 10 | * @param {number} y1 起始y 11 | * @param {number} x2 结束x 12 | * @param {number} y2 结束y 13 | * @return {number} 弧度 14 | */ 15 | export default function getAngle(x1, y1, x2, y2) { 16 | // cos(θ) = (x1x2+y1y2)/[√(x1²+y1²)*√(x2²+y2²)] 17 | let angle = Math.acos( 18 | (x1 * x2 + y1 * y2) / Math.sqrt(x1 * x1 + y1 * y1) / Math.sqrt(x2 * x2 + y2 * y2) 19 | ); 20 | 21 | // 有向线段内积,判断左右 22 | // (xb - xa) * (yc - ya) - (xc - xa) * (yb - ya); 23 | if (x1 * y2 - x2 * y1 < 0) { 24 | angle = 2 * Math.PI - angle; 25 | } 26 | 27 | return angle; 28 | } 29 | -------------------------------------------------------------------------------- /src/math/getBezierQ2Point.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 获取贝塞尔曲线上的点 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | /** 7 | * 获取贝塞尔曲线上的点 8 | * 9 | * @param {Object} p0 p0 10 | * @param {Object} p1 p1 11 | * @param {Object} p2 p2 12 | * @param {number} t t 13 | * @return {Object} 点对象 14 | */ 15 | export default function getPoint(p0, p1, p2, t) { 16 | return { 17 | x: p0.x * Math.pow(1 - t, 2) + 2 * p1.x * t * (1 - t) + p2.x * Math.pow(t, 2), 18 | y: p0.y * Math.pow(1 - t, 2) + 2 * p1.y * t * (1 - t) + p2.y * Math.pow(t, 2) 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /src/math/getBezierQ2T.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 获取贝塞尔曲线的参数t 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import bezierQ2Equation from './bezierQ2Equation'; 7 | import getPoint from './getBezierQ2Point'; 8 | 9 | /** 10 | * 获取贝塞尔曲线的参数t 11 | * 12 | * @param {Object} p0 p0 13 | * @param {Object} p1 p1 14 | * @param {Object} p2 p2 15 | * @param {Object} p 分割点 16 | * @return {number} t值 17 | */ 18 | export default function getBezierQ2T(p0, p1, p2, p) { 19 | 20 | // 极端情况 21 | if (Math.abs(p.x - p0.x) < 0.001 && Math.abs(p.y - p0.y) < 0.001) { 22 | return 0; 23 | } 24 | else if (Math.abs(p.x - p2.x) < 0.001 && Math.abs(p.y - p2.y) < 0.001) { 25 | return 1; 26 | } 27 | 28 | let result = bezierQ2Equation( 29 | p0.x + p2.x - 2 * p1.x + p0.y + p2.y - 2 * p1.y, 30 | 2 * (p1.x - p0.x) + 2 * (p1.y - p0.y), 31 | p0.x + p0.y - p.x - p.y 32 | ); 33 | 34 | if (!result) { 35 | return false; 36 | } 37 | 38 | let t = false; 39 | 40 | // 这里需要验证下,有些情况下公式有解,但是点不在曲线上。。 41 | for (let i = 0, l = result.length; i < l; i++) { 42 | let pt = getPoint(p0, p1, p2, result[i]); 43 | if (Math.abs(pt.x - p.x) < 0.001 && Math.abs(pt.y - p.y) < 0.001) { 44 | t = result[i]; 45 | break; 46 | } 47 | } 48 | 49 | return t; 50 | } 51 | -------------------------------------------------------------------------------- /src/math/quadraticEquation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 求解一元二次方程 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | /** 7 | * 求解一元二次方程 8 | * 9 | * @param {number} a a系数 10 | * @param {number} b b系数 11 | * @param {number} c c系数 12 | * @return {Array} 系数解 13 | */ 14 | export default function quadraticEquation(a, b, c) { 15 | 16 | if (a === 0) { 17 | return [-c / b]; 18 | } 19 | 20 | let x1; 21 | let x2; 22 | 23 | if (b === 0) { 24 | if (c === 0) { 25 | return [0]; 26 | } 27 | 28 | if (a > 0 && c < 0 || a < 0 && c > 0) { 29 | x2 = Math.sqrt(-c / a); 30 | return [x2, -x2]; 31 | } 32 | 33 | return false; 34 | } 35 | 36 | if (c === 0) { 37 | return [0, -b / a]; 38 | } 39 | 40 | let b4ac = Math.pow(b, 2) - 4 * a * c; 41 | 42 | if (b4ac >= 0) { 43 | x1 = (-b + Math.sqrt(b4ac, 2)) / a / 2; 44 | x2 = (-b - Math.sqrt(b4ac, 2)) / a / 2; 45 | if (x1 === x2) { 46 | return [x1]; 47 | } 48 | 49 | return [x1, x2]; 50 | } 51 | 52 | return false; 53 | } 54 | -------------------------------------------------------------------------------- /src/render/Camera.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 视角对象,用来控制平移和缩放 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | export default class Camera { 7 | 8 | /** 9 | * 视角对象 10 | * 11 | * @param {Object} center 中心点 12 | * @param {number} scale 缩放级别 13 | * @param {number} ratio 缩放比例 14 | * @constructor 15 | */ 16 | constructor(center, scale, ratio) { 17 | this.reset(center, scale, ratio); 18 | } 19 | 20 | /** 21 | * 重置camera 22 | * 23 | * @param {Object} center 中心点 24 | * @param {number} scale 缩放级别 25 | * @param {number} ratio 缩放比例 26 | */ 27 | reset(center, scale, ratio) { 28 | this.center = center || { 29 | x: 0, 30 | y: 0 31 | }; 32 | this.scale = scale || 1; 33 | this.ratio = ratio || 1; 34 | } 35 | 36 | /** 37 | * 注销 38 | */ 39 | dispose() { 40 | this.center = this.ratio = this.scale = null; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/render/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 创建图形渲染组件 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import Render from './Render'; 7 | import Controller from './Controller'; 8 | 9 | export default { 10 | 11 | /** 12 | * 创建一个render 13 | * 14 | * @param {HTMLElement} main 主控区域 15 | * @param {Object} options 创建参数 16 | * @param {Controller} options.controller 控制器 17 | * @return {Render} render对象 18 | */ 19 | create(main, options = {}) { 20 | let render = new Render(main, options); 21 | let controller = options.controller; 22 | delete options.controller; 23 | 24 | // 设置render的默认控制器 25 | if (!controller) { 26 | controller = new Controller(); 27 | } 28 | 29 | controller.setRender(render); 30 | return render; 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /src/render/shape/Axis.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 坐标轴绘制 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | import shape from './Shape'; 6 | import drawAxis from '../util/drawAxis'; 7 | 8 | export default shape.derive({ 9 | 10 | type: 'axis', 11 | 12 | adjust(shape, camera) { 13 | let center = camera.center; 14 | let ratio = camera.ratio; 15 | 16 | shape.unitsPerEm *= ratio; 17 | 18 | let metrics = shape.metrics; 19 | Object.keys(metrics).forEach(function (line) { 20 | metrics[line] *= ratio; 21 | }); 22 | 23 | shape.x = ratio * (shape.x - center.x) + center.x; 24 | shape.y = ratio * (shape.y - center.y) + center.y; 25 | 26 | return shape; 27 | }, 28 | 29 | isIn(shape, x, y) { 30 | return false; 31 | }, 32 | 33 | getBound(shape) { 34 | return { 35 | x: shape.x, 36 | y: shape.y, 37 | width: 0, 38 | height: 0 39 | }; 40 | }, 41 | 42 | draw(ctx, shape, camera) { 43 | shape.scale = camera.scale; 44 | ctx.save(); 45 | drawAxis(ctx, shape); 46 | ctx.restore(); 47 | ctx.beginPath(); 48 | } 49 | }); 50 | -------------------------------------------------------------------------------- /src/render/shape/BezierCurve.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file bezier曲线绘制 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import shape from './Shape'; 7 | 8 | import isInsidePath from 'graphics/isInsidePath'; 9 | import pathAdjust from 'graphics/pathAdjust'; 10 | import computeBoundingBox from 'graphics/computeBoundingBox'; 11 | 12 | export default shape.derive({ 13 | 14 | type: 'beziercurve', 15 | 16 | adjust(shape, camera) { 17 | let ratio = camera.ratio; 18 | let x = camera.center.x; 19 | let y = camera.center.y; 20 | pathAdjust(shape.points, ratio, ratio, -x, -y); 21 | pathAdjust(shape.points, 1, 1, x, y); 22 | }, 23 | 24 | move(shape, mx, my) { 25 | pathAdjust(shape.points, 1, 1, mx, my); 26 | return shape; 27 | }, 28 | 29 | getRect(shape) { 30 | return computeBoundingBox.computePath(shape.points); 31 | }, 32 | 33 | isIn(shape, x, y) { 34 | let bound = computeBoundingBox.computePath(shape.points); 35 | if ( 36 | x <= bound.x + bound.width 37 | && x >= bound.x 38 | && y <= bound.y + bound.height 39 | && y >= bound.y 40 | ) { 41 | 42 | return isInsidePath(shape.points, { 43 | x, 44 | y 45 | }); 46 | } 47 | return false; 48 | }, 49 | 50 | draw(ctx, shape) { 51 | let points = shape.points; 52 | // 二次bezier曲线 53 | if (points.length === 3) { 54 | ctx.moveTo(points[0].x, points[0].y); 55 | ctx.quadraticCurveTo(points[1].x, points[1].y, points[2].x, points[2].y); 56 | } 57 | // 三次bezier曲线 58 | else if (points.length === 4) { 59 | ctx.moveTo(points[0].x, points[0].y); 60 | ctx.bezierCurveTo( 61 | points[1].x, points[1].y, 62 | points[2].x, points[2].y, 63 | points[3].x, points[3].y 64 | ); 65 | } 66 | } 67 | }); 68 | -------------------------------------------------------------------------------- /src/render/shape/Circle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 圆绘制 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | import shape from './Shape'; 6 | 7 | export default shape.derive({ 8 | 9 | type: 'circle', 10 | 11 | getRect(shape) { 12 | return { 13 | x: shape.x - shape.r, 14 | y: shape.y - shape.r, 15 | width: 2 * shape.r, 16 | height: 2 * shape.r 17 | }; 18 | }, 19 | 20 | isIn(shape, x, y) { 21 | return Math.pow(shape.x - x, 2) + Math.pow(shape.y - y, 2) <= Math.pow(shape.r, 2); 22 | }, 23 | 24 | 25 | draw(ctx, shape) { 26 | ctx.moveTo(shape.x + shape.r, shape.y); 27 | ctx.arc(shape.x, shape.y, shape.r, 0, Math.PI * 2, true); 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /src/render/shape/CirclePoint.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 圆形点绘制 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import shape from './Shape'; 7 | const POINT_SIZE = 2; 8 | 9 | export default shape.derive({ 10 | 11 | type: 'cpoint', 12 | 13 | getRect(shape) { 14 | let size = shape.size || POINT_SIZE; 15 | return { 16 | x: shape.x - size, 17 | y: shape.y - size, 18 | width: 2 * size, 19 | height: 2 * size 20 | }; 21 | }, 22 | 23 | isIn(shape, x, y) { 24 | let size = shape.size || POINT_SIZE; 25 | return Math.pow(shape.x - x, 2) + Math.pow(shape.y - y, 2) <= Math.pow(size * 2, 2); 26 | }, 27 | 28 | draw(ctx, shape) { 29 | let size = shape.size || POINT_SIZE; 30 | let x = Math.round(shape.x); 31 | let y = Math.round(shape.y); 32 | 33 | ctx.moveTo(x + size, y); 34 | ctx.arc(x, y, size, 0, Math.PI * 2, true); 35 | } 36 | }); 37 | -------------------------------------------------------------------------------- /src/render/shape/Glyf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 简单字形绘制 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import shape from './Shape'; 7 | import drawPath from '../util/drawPath'; 8 | 9 | export default shape.derive({ 10 | 11 | type: 'glyf', 12 | 13 | adjust(shape, camera) { 14 | let center = camera.center; 15 | let ratio = camera.ratio; 16 | 17 | shape.x = ratio * (shape.x - center.x) + center.x; 18 | shape.y = ratio * (shape.y - center.y) + center.y; 19 | 20 | return shape; 21 | 22 | }, 23 | 24 | move(shape, mx, my) { 25 | shape.x += mx; 26 | shape.y += my; 27 | 28 | return shape; 29 | }, 30 | 31 | getRect(shape) { 32 | return false; 33 | }, 34 | 35 | isIn(shape, x, y) { 36 | return false; 37 | }, 38 | 39 | draw(ctx, shape, camera) { 40 | 41 | ctx.save(); 42 | ctx.translate(shape.x, shape.y); 43 | ctx.scale(camera.scale, -camera.scale); 44 | 45 | let transform = shape.transform; 46 | ctx.transform( 47 | transform.a, 48 | transform.b, 49 | transform.c, 50 | transform.d, 51 | transform.e, 52 | transform.f 53 | ); 54 | 55 | let contours = shape.glyf.contours; 56 | for (let i = 0, l = contours.length; i < l; i++) { 57 | drawPath(ctx, contours[i]); 58 | } 59 | 60 | ctx.restore(); 61 | } 62 | }); 63 | -------------------------------------------------------------------------------- /src/render/shape/Graduation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 刻度线绘制 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import shape from './Shape'; 7 | import drawGraduation from '../util/drawGraduation'; 8 | 9 | export default shape.derive({ 10 | 11 | type: 'graduation', 12 | 13 | adjust(shape, camera) { 14 | return shape; 15 | }, 16 | 17 | isIn(shape, x, y) { 18 | return false; 19 | }, 20 | 21 | draw(ctx, shape, camera) { 22 | 23 | shape.scale = camera.scale; 24 | ctx.save(); 25 | // 绘制刻度线 26 | drawGraduation(ctx, shape.config); 27 | ctx.restore(); 28 | ctx.beginPath(); 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /src/render/shape/Path.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 路径绘制 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import shape from './Shape'; 7 | import isInsidePath from 'graphics/isInsidePath'; 8 | import pathAdjust from 'graphics/pathAdjust'; 9 | import drawPath from '../util/drawPath'; 10 | import computeBoundingBox from 'graphics/computeBoundingBox'; 11 | 12 | export default shape.derive({ 13 | 14 | type: 'path', 15 | 16 | adjust(shape, camera) { 17 | let ratio = camera.ratio; 18 | let x = camera.center.x; 19 | let y = camera.center.y; 20 | pathAdjust(shape.points, ratio, ratio, -x, -y); 21 | pathAdjust(shape.points, 1, 1, x, y); 22 | }, 23 | 24 | move(shape, mx, my) { 25 | pathAdjust(shape.points, 1, 1, mx, my); 26 | return shape; 27 | }, 28 | 29 | getRect(shape) { 30 | return computeBoundingBox.computePath(shape.points); 31 | }, 32 | 33 | isIn(shape, x, y) { 34 | let bound = computeBoundingBox.computePath(shape.points); 35 | if ( 36 | x <= bound.x + bound.width 37 | && x >= bound.x 38 | && y <= bound.y + bound.height 39 | && y >= bound.y 40 | ) { 41 | 42 | return isInsidePath(shape.points, { 43 | x, 44 | y 45 | }); 46 | } 47 | return false; 48 | }, 49 | 50 | 51 | draw(ctx, shape) { 52 | drawPath(ctx, shape.points); 53 | } 54 | }); 55 | -------------------------------------------------------------------------------- /src/render/shape/Point.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 点绘制 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import shape from './Shape'; 7 | const POINT_SIZE = 6; // 控制点的大小 8 | 9 | export default shape.derive({ 10 | 11 | type: 'point', 12 | 13 | getRect(shape) { 14 | let size = shape.size || POINT_SIZE; 15 | return { 16 | x: shape.x - size / 2, 17 | y: shape.y - size / 2, 18 | width: size, 19 | height: size 20 | }; 21 | }, 22 | 23 | isIn(shape, x, y) { 24 | let size = shape.size || POINT_SIZE; 25 | let w = size; 26 | let h = size; 27 | return x <= shape.x + w 28 | && x >= shape.x - w 29 | && y <= shape.y + h 30 | && y >= shape.y - h; 31 | }, 32 | 33 | draw(ctx, shape) { 34 | 35 | let x = Math.round(shape.x); 36 | let y = Math.round(shape.y); 37 | let size = shape.size || POINT_SIZE; 38 | let w = size / 2; 39 | let h = size / 2; 40 | ctx.moveTo(x - w, y - h); 41 | ctx.lineTo(x + w, y - h); 42 | ctx.lineTo(x + w, y + h); 43 | ctx.lineTo(x - w, y + h); 44 | ctx.lineTo(x - w, y - h); 45 | } 46 | }); 47 | -------------------------------------------------------------------------------- /src/render/shape/Rect.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 矩形绘制 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import shape from './Shape'; 7 | import dashedLineTo from '../util/dashedLineTo'; 8 | 9 | export default shape.derive({ 10 | 11 | type: 'rect', 12 | 13 | getRect(shape) { 14 | return shape; 15 | }, 16 | 17 | isIn(shape, x, y) { 18 | let w = shape.width; 19 | let h = shape.height; 20 | return x <= shape.x + w 21 | && x >= shape.x 22 | && y <= shape.y + h 23 | && y >= shape.y; 24 | }, 25 | 26 | draw(ctx, shape) { 27 | 28 | let x = Math.round(shape.x); 29 | let y = Math.round(shape.y); 30 | let w = Math.round(shape.width); 31 | let h = Math.round(shape.height); 32 | 33 | if (shape.dashed) { 34 | dashedLineTo(ctx, x, y, x + w, y); 35 | dashedLineTo(ctx, x + w, y, x + w, y + h); 36 | dashedLineTo(ctx, x + w, y + h, x, y + h); 37 | dashedLineTo(ctx, x, y + h, x, y); 38 | } 39 | else { 40 | ctx.moveTo(x, y); 41 | ctx.lineTo(x + w, y); 42 | ctx.lineTo(x + w, y + h); 43 | ctx.lineTo(x, y + h); 44 | ctx.lineTo(x, y); 45 | } 46 | } 47 | }); 48 | 49 | -------------------------------------------------------------------------------- /src/render/shape/Text.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 文本节点绘制 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import shape from './Shape'; 7 | 8 | export default shape.derive({ 9 | 10 | type: 'text', 11 | 12 | getRect(shape) { 13 | return false; 14 | }, 15 | 16 | isIn(shape, x, y) { 17 | return false; 18 | }, 19 | 20 | draw(ctx, shape) { 21 | ctx.save(); 22 | let x = Math.round(shape.x); 23 | let y = Math.round(shape.y); 24 | if (shape.fillColor) { 25 | ctx.fillStyle = shape.fillColor; 26 | } 27 | ctx.fillText(shape.text, x, y); 28 | ctx.restore(); 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /src/render/shape/support.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 支持的shape集合 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import circle from './Circle'; 7 | import cpoint from './CirclePoint'; 8 | import point from './Point'; 9 | import rect from './Rect'; 10 | import polygon from './Polygon'; 11 | import font from './Font'; 12 | import path from './Path'; 13 | import axis from './Axis'; 14 | import graduation from './Graduation'; 15 | import line from './Line'; 16 | import beziercurve from './BezierCurve'; 17 | import gridarrow from './GridArrow'; 18 | import text from './Text'; 19 | 20 | export default { 21 | circle, 22 | cpoint, 23 | point, 24 | rect, 25 | polygon, 26 | font, 27 | path, 28 | axis, 29 | graduation, 30 | line, 31 | beziercurve, 32 | gridarrow, 33 | text 34 | }; 35 | -------------------------------------------------------------------------------- /src/render/util/dashedLineTo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 绘制虚线段 3 | * @author mengke01(kekee000@gmail.com) 4 | * 5 | * modify from: 6 | * zrender/src/shape/util 7 | */ 8 | 9 | /** 10 | * 绘制虚线段 11 | * 12 | * @param {CanvasRenderingContext2D} ctx canvascontext 13 | * @param {number} x1 x1坐标 14 | * @param {number} y1 y1坐标 15 | * @param {number} x2 x2坐标 16 | * @param {number} y2 y2坐标 17 | * @param {?number} dashLength 虚线长度 18 | */ 19 | export default function dashedLineTo(ctx, x1, y1, x2, y2, dashLength) { 20 | 21 | dashLength = typeof dashLength !== 'number' 22 | ? 2 23 | : dashLength; 24 | 25 | let dx = x2 - x1; 26 | let dy = y2 - y1; 27 | 28 | let numDashes = Math.floor( 29 | Math.sqrt(dx * dx + dy * dy) / dashLength 30 | ); 31 | 32 | dx = dx / numDashes; 33 | dy = dy / numDashes; 34 | 35 | let flag = true; 36 | 37 | for (let i = 0; i < numDashes; ++i) { 38 | if (flag) { 39 | ctx.moveTo(x1, y1); 40 | } 41 | else { 42 | ctx.lineTo(x1, y1); 43 | } 44 | 45 | flag = !flag; 46 | x1 += dx; 47 | y1 += dy; 48 | } 49 | ctx.lineTo(x2, y2); 50 | } 51 | -------------------------------------------------------------------------------- /src/render/util/drawPath.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 绘制contour曲线 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | /** 7 | * ctx绘制轮廓 8 | * 9 | * @param {CanvasRenderingContext2D} ctx canvas会话 10 | * @param {Array} contour 轮廓序列 11 | */ 12 | export default function drawPath(ctx, contour) { 13 | 14 | let curPoint; 15 | let prevPoint; 16 | let nextPoint; 17 | for (let i = 0, l = contour.length; i < l; i++) { 18 | curPoint = contour[i]; 19 | prevPoint = i === 0 ? contour[l - 1] : contour[i - 1]; 20 | nextPoint = i === l - 1 ? contour[0] : contour[i + 1]; 21 | 22 | // 起始坐标 23 | if (i === 0) { 24 | if (curPoint.onCurve) { 25 | ctx.moveTo(curPoint.x, curPoint.y); 26 | } 27 | else { 28 | if (prevPoint.onCurve) { 29 | ctx.moveTo(prevPoint.x, prevPoint.y); 30 | } 31 | else { 32 | ctx.moveTo((prevPoint.x + curPoint.x) / 2, (prevPoint.y + curPoint.y) / 2); 33 | } 34 | } 35 | } 36 | 37 | // 直线 38 | if (curPoint.onCurve && nextPoint.onCurve) { 39 | ctx.lineTo(nextPoint.x, nextPoint.y); 40 | } 41 | else if (!curPoint.onCurve) { 42 | if (nextPoint.onCurve) { 43 | ctx.quadraticCurveTo(curPoint.x, curPoint.y, nextPoint.x, nextPoint.y); 44 | } 45 | else { 46 | ctx.quadraticCurveTo( 47 | curPoint.x, curPoint.y, 48 | (curPoint.x + nextPoint.x) / 2, (curPoint.y + nextPoint.y) / 2 49 | ); 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/render/util/guid.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 产生唯一的guid 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | let counter = 0; 7 | 8 | /** 9 | * 生成guid 10 | * 11 | * @param {string} prefix 前缀 12 | * @return {string} guid字符串 13 | */ 14 | export default function guid(prefix) { 15 | return (prefix || 'render') + '-' + (counter++); 16 | } -------------------------------------------------------------------------------- /test/spec/graphics/vector.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file vector 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | import assert from 'assert'; 7 | import vector from 'graphics/vector'; 8 | 9 | describe('测试点到直线距离', function () { 10 | 11 | it('test verital', function () { 12 | let p0 = {x: 10, y: 10}; 13 | let p1 = {x: 20, y: 10}; 14 | let p = {x: 50, y: 10}; 15 | assert.equal(vector.getDist(p0, p1, p), 0); 16 | }); 17 | 18 | it('test hoz', function () { 19 | let p0 = {x: 10, y: 10}; 20 | let p1 = {x: 10, y: 20}; 21 | let p = {x: 10, y: 80}; 22 | assert.equal(vector.getDist(p0, p1, p), 0); 23 | }); 24 | 25 | it('test sameline', function () { 26 | let p0 = {x: 10, y: 10}; 27 | let p1 = {x: 50, y: 50}; 28 | let p = {x: 25, y: 25}; 29 | assert.equal(vector.getDist(p0, p1, p), 0); 30 | 31 | 32 | p0 = {x: 10, y: 10}; 33 | p1 = {x: 50, y: 50}; 34 | p = {x: 80, y: 80}; 35 | assert.equal(vector.getDist(p0, p1, p), 0); 36 | }); 37 | 38 | it('test hoz dist', function () { 39 | let p0 = {x: 10, y: 10}; 40 | let p1 = {x: 10, y: 40}; 41 | let p = {x: 50, y: 47}; 42 | assert.equal(vector.getDist(p0, p1, p), 40); 43 | 44 | 45 | p0 = {x: 45, y: 50}; 46 | p1 = {x: 50, y: 50}; 47 | p = {x: 90, y: 25}; 48 | assert.equal(vector.getDist(p0, p1, p), 25); 49 | }); 50 | 51 | 52 | it('test dist', function () { 53 | let p0 = {x: 50, y: 50}; 54 | let p1 = {x: 0, y: 0}; 55 | let p = {x: 50, y: 0}; 56 | assert.equal(vector.getDist(p0, p1, p).toFixed(2), 35.36); 57 | 58 | 59 | p0 = {x: 50, y: 50}; 60 | p1 = {x: 0, y: 0}; 61 | p = {x: 50, y: 0}; 62 | assert.equal(vector.getDist(p0, p1, p).toFixed(2), 35.36); 63 | }); 64 | }); -------------------------------------------------------------------------------- /test/spec/math/getBezierQ2T.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file getBezierQ2T 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | 6 | 7 | import assert from 'assert'; 8 | import getBezierQ2T from 'math/getBezierQ2T'; 9 | 10 | describe('获得bezier曲线坐标t', function () { 11 | 12 | it('test false', function () { 13 | let t = getBezierQ2T({"x":786,"y":638},{"x":673,"y":545},{"x":526,"y":545}, {x: 544.779145, y: 644.574325}); 14 | assert.equal(t, false); 15 | }); 16 | 17 | it('test start', function () { 18 | let t = getBezierQ2T({"x":786,"y":638},{"x":673,"y":545},{"x":526,"y":545}, {"x":786,"y":638}); 19 | assert.equal(t, 0); 20 | }); 21 | 22 | it('test end', function () { 23 | let t = getBezierQ2T({"x":786,"y":638},{"x":673,"y":545},{"x":526,"y":545}, {"x":526,"y":545}); 24 | assert.equal(t, 1); 25 | }); 26 | 27 | it('test middle', function () { 28 | let t = getBezierQ2T({"x":786,"y":638},{"x":673,"y":545},{"x":526,"y":545}, {x: 664.5, y: 568.25}); 29 | assert.equal(t, 0.5); 30 | }); 31 | 32 | }); 33 | --------------------------------------------------------------------------------