├── .gitignore ├── LICENSE ├── README.md ├── html ├── NotDefault.woff2 ├── base-split.woff ├── document.js ├── document.less ├── eva-matisse-classic-moji-list.js ├── index.html ├── layouts.js ├── logo@2x.png ├── make.js ├── makebmp.js ├── transform-func.js ├── ui-switch.vue.js └── ui-tabs.vue.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | Noto_Serif_SC/* 2 | package-lock.json 3 | document.css 4 | 参考/* 5 | fonts/* 6 | un/* 7 | vercel.json 8 | api/* 9 | server.js 10 | dest/* 11 | html/dest/* 12 | html/Gruntfile.js 13 | html/package.json 14 | html/logo* 15 | vue.2.6.11.min.js 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 卜卜口 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 🐧 福音战士标题生成器 - Evangelion Title Card Generator 4 | 又一个福音战士标题生成器 5 | 6 | ## 在线地址 7 | https://lab.magiconch.com/eva-title/ 8 | 9 | 2022-11-26 不匹配字型调整为空白显示,请确保字型存在 #31 10 | 11 | ## 功能 12 | - [x] 自定义文字 13 | - [x] 黑底白字 14 | - [x] 白底黑字 15 | - [x] 黑底红字 16 | - [x] 模糊 17 | - [x] 噪点 18 | - [x] 锐化 19 | - [x] 模仿95版画面 20 | - [x] 文字转繁体 21 | - [x] 提示当前缺失字型 `2022-07-08` 22 | - [x] 一键尝试替换缺失字型 `2022-07-08` 23 | - [x] 输出画面比例 `2022-09-03` 24 | 25 | ## 说明 26 | - 本机安装 **Matisse-EB** 字体的情况会得到最佳生成体验 27 | - 实在找不到替换文字时可以偷懒安装一份 **思源宋体 Heavy** 字型作为 `Fallbacks` 28 | - 字体授权来自 **fontwork**,仅供个人使用。再利用需自行购买授权 29 | 30 | 31 | 32 | ## 已解决的大问题 33 | - [x] Safari 下 EVAMatisseClassic 字体无法正常绘制 34 | - [x] Firefox 下 canvas 无法正常绘制 35 | 36 | fix: 解决了firefox的问题,可能解决了safari的问题 [#10](https://github.com/itorr/eva-title/pull/10) 37 | 38 | 所以现在 iOS、Mac Safari、Firefox 都可以用啦~ 39 | 40 | ## 字体 41 | https://mojimo.jp/eva/ 42 | 43 | ## GitHub 44 | https://github.com/itorr/eva-title 45 | 46 | ## 标题参考 47 | https://zh.wikipedia.org/wiki/新世纪福音战士 48 | -------------------------------------------------------------------------------- /html/NotDefault.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itorr/eva-title/f46226ee871b6b9ceb919d5e888ac8a795f5def5/html/NotDefault.woff2 -------------------------------------------------------------------------------- /html/base-split.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itorr/eva-title/f46226ee871b6b9ceb919d5e888ac8a795f5def5/html/base-split.woff -------------------------------------------------------------------------------- /html/document.js: -------------------------------------------------------------------------------- 1 | 2 | const htmlEl = document.documentElement; 3 | const isChrome = /Chrome/.test(navigator.userAgent); 4 | 5 | htmlEl.setAttribute('data-is-chrome',isChrome); 6 | 7 | const style = document.createElement('style'); 8 | document.head.appendChild(style); 9 | // let fontAPI = 'http://192.168.31.7:8003/api/fontmin'; 10 | let fontAPI = `https://${location.hostname}/api/fontmin`; 11 | 12 | // fontAPI = 'https://s6.magiconch.com/api/fontmin'; 13 | // fontAPI = 'http://localhost:60912/api/fontmin'; 14 | 15 | // fontAPI = 'https://eva-title-server.vercel.app/api/fontmin'; 16 | 17 | const blockMojiRegex = /\s/g; 18 | 19 | 20 | const checkFont = (fontName,weight=100)=>{ 21 | const canvas = document.createElement('canvas'); 22 | const w = 18; 23 | canvas.width = w; 24 | canvas.height = w; 25 | const ctx = canvas.getContext('2d'); 26 | document.body.appendChild(canvas); 27 | 28 | ctx.font = `${weight} ${w}px ${fontName},sans-serif`; 29 | ctx.fillStyle = '#000'; 30 | ctx.lineCap = 'round'; 31 | ctx.lineJoin = 'round'; 32 | ctx.textAlign = 'left'; 33 | ctx.textBaseline = 'bottom'; 34 | ctx.clearRect(0,0,w,w); 35 | ctx.fillText( 36 | '饑', 37 | 0, w 38 | ); 39 | const pixel = ctx.getImageData(0,0,w,w); 40 | const d = pixel.data; 41 | 42 | let aa = 0; 43 | for(let i=0;i 120; 53 | let haveSourceHanSerifCNHeavy = checkFont('SourceHanSerifCN-Heavy',800) > 150; 54 | 55 | 56 | let debug = /192\.168/.test(location.origin); 57 | 58 | 59 | if(debug){ 60 | // fontWeight = 100; 61 | fontAPI = 'http://localhost:60912/api/fontmin'; 62 | haveMatisse = false; 63 | // baseFontFamilyName = 'baseSplit,serif'; 64 | } 65 | 66 | 67 | const getFontFromText = (name,text,onOver=_=>{})=>{ 68 | if(!text) return requestAnimationFrame(onOver); 69 | if(haveMatisse) return requestAnimationFrame(onOver); 70 | 71 | text = text.replace(blockMojiRegex,''); 72 | text += '0'; 73 | text = Array.from(new Set(text)).sort().join(''); 74 | // console.log(str2utf8(text)) 75 | // console.log(utf82str(str2utf8(text))) 76 | text = diffDefaultMoji(text); 77 | // console.log({text}) 78 | if(!text) return requestAnimationFrame(onOver); 79 | 80 | const unicode = str2utf8(text).join(); 81 | const fontURL = `${fontAPI}?name=${name}&unicode=${unicode}`; 82 | 83 | loadFont(name,fontURL,_=>{ 84 | onOver(_) 85 | // style.innerHTML = `html {font-family: a123;}`; 86 | }) 87 | } 88 | const loadFont = async (fontName,fontURL,callback) => { 89 | if(haveMatisse) return requestAnimationFrame(callback); 90 | const fontFace = new FontFace(fontName, `url(${fontURL})`); 91 | fontFace.load().then(fontFace => { 92 | document.fonts.add(fontFace); 93 | callback(fontFace); 94 | }).catch(e=>{ 95 | // console.log(e); 96 | callback(); 97 | }) 98 | }; 99 | function str2utf8(str) { 100 | return str.split('').map(s=>s.charCodeAt(0)) 101 | } 102 | function utf82str(str) { 103 | return String.fromCharCode.apply(null,Array.from(str)) 104 | } 105 | 106 | 107 | const deepCopy=o=>JSON.parse(JSON.stringify(o)); 108 | 109 | const inputEl = document.querySelector('textarea'); 110 | const checkboxEl = document.querySelector('input'); 111 | const outputEl = document.querySelector('#out'); 112 | 113 | 114 | const defaultMojiPlus = ' \n,-./01234567890:?ABCDEFGHILMNOPRSTUVabcdefghijklmnoprstuvwxyz“”、。「」いかくけげしせただちてでとなのはめもらるわをんアイカグシスゼダネバフマルレー一下不世中了京人今他伍作使來例価侵値僅先入八六其决况出到劳化匹博原参參叫可吃問喜嘗嘘器噪嚴四在型士壱太奇字存实室實市座庵弐当後徒微心情成我战戦戰拾持掃授排換支攷文新日明替最权来東案桌森標模樣歡求決沈浏海瀏版生用界發的監看督石神福秀章端第糊系終繁纪统网者臭螺襲覽览試話誕請议请跡輸轉逃选遇還郎配重野銳键間雨雷電面音頭題页项香驗验體魂鳴麦黙點🏼👩!,'.split(''); 115 | 116 | const getMoji = _=>{ 117 | let v = defaultMojiPlus+layouts.map(a=>[a.inputs.map(t=>t.placeholder),a.exemples]).flat().join(); 118 | // console.log(v) 119 | // v += document.querySelector('body').textContent; 120 | return v; 121 | }; 122 | 123 | let defaultMoji = Array.from(new Set(getMoji())).sort(); 124 | 125 | // console.log(defaultMoji.join('')); 126 | 127 | // if(ios || !isChrome){ 128 | // defaultMoji = []; 129 | // } 130 | 131 | if(debug){ 132 | const unicode = str2utf8(defaultMoji.join('')).join(); 133 | console.log(`${fontAPI}?name=${fontFamilyName}&type=woff&unicode=${unicode}`); 134 | } 135 | 136 | const diffDefaultMoji = text=>{ 137 | return text.split('').filter(moji=>!defaultMoji.includes(moji)).join('').replace(/\s/g,'') 138 | }; 139 | 140 | 141 | const texts = [ 142 | '', 143 | '', 144 | '', 145 | '', 146 | ] 147 | const defaultConfig = { 148 | blur:true, 149 | height:480, 150 | shadow:true, 151 | convolute: false, 152 | retina:true, 153 | plan:undefined, 154 | noise:true, 155 | outputRatio:1.334, 156 | // inverse:false,// Math.random()>0.9, 157 | }; 158 | const outputRatios = [ 159 | { 160 | value: 1.334, 161 | text: '4:3' 162 | }, 163 | { 164 | value: 1.778, 165 | text: '16:9' 166 | }, 167 | { 168 | value: 1, 169 | text: '3:3' 170 | }, 171 | { 172 | value: 1.25, 173 | text: '5:4' 174 | }, 175 | { 176 | value: 1.5, 177 | text: '3:2' 178 | }, 179 | ] 180 | const types = [ 181 | { 182 | value: undefined, 183 | text:'DVD' 184 | }, 185 | { 186 | value: 95, 187 | text: '95' 188 | } 189 | ] 190 | const plans = [ 191 | { 192 | value:undefined, 193 | text:'黑白' 194 | }, 195 | { 196 | value:'wb', 197 | text:'白黑' 198 | }, 199 | { 200 | value:'br', 201 | text:'黑红' 202 | }, 203 | { 204 | value:'rw', 205 | text:'红白' 206 | }, 207 | { 208 | value:'by', 209 | text:'黑黄' 210 | }, 211 | // { 212 | // value:'yb', 213 | // text:'黄黑' 214 | // } 215 | ] 216 | const data ={ 217 | layout:null, 218 | layouts:[], 219 | config:deepCopy(defaultConfig), 220 | texts, 221 | loading:true, 222 | lastAllText:'', 223 | output: null, 224 | downloadFilename: null, 225 | }; 226 | const Layouts = {} 227 | layouts.forEach(layout=>{ 228 | Layouts[layout.id] = layout; 229 | }); 230 | 231 | const defaultTitle = document.title; 232 | 233 | 234 | const textOrigin = '扫袭'; 235 | const textBefore = '掃襲'; 236 | 237 | const textFilter = text=>{ 238 | return text; 239 | }; 240 | 241 | 242 | 243 | 244 | 245 | const app = new Vue({ 246 | el:'.app', 247 | data, 248 | methods:{ 249 | make(){ 250 | clearTimeout(make.timer); 251 | 252 | make.timer = setTimeout(_=>{ 253 | const texts = this.layout.inputs.map((input,index)=>{ 254 | const {type} = input; 255 | if(type==='tab'){ 256 | return this.texts[index]; 257 | } 258 | return textFilter(this.texts[index] || input.placeholder) 259 | }); 260 | 261 | this.loading = true; 262 | getFontFromText(fontFamilyName,texts.join(''), _=>{ 263 | make({ 264 | outputCanvas: this.$refs['canvas'], 265 | texts, 266 | config: this.config, 267 | layout: this.layout 268 | }); 269 | this.loading = false; 270 | this.lastAllText = this.allText; 271 | }); 272 | },200); 273 | }, 274 | setLayout(_layout,noRoute){ 275 | this.layout = _layout; 276 | const {inputs,config} = _layout; 277 | // console.log(Object.assign({},defaultConfig,config)) 278 | this.config = Object.assign({},defaultConfig,config); 279 | this.setDefaultTexts(_layout); 280 | 281 | const { id } = _layout; 282 | 283 | const title = `${_layout.title} - ${defaultTitle}`; 284 | 285 | document.title = title; 286 | 287 | if(!noRoute) history.replaceState({}, title, `./?layout=${encodeURIComponent(id)}`); 288 | }, 289 | setExemple(exemple){ 290 | // console.log({exemple}) 291 | exemple.forEach((t,i)=>{ 292 | // this.texts[i]=t 293 | this.$set(this.texts,i,t); 294 | }); 295 | this.make(); 296 | }, 297 | setDefaultTexts(layout){ 298 | const {inputs} = layout; 299 | this.texts = inputs.map(input=>{ 300 | const {type} = input; 301 | if(type === 'tab'){ 302 | return 0//input.options[0] 303 | } 304 | return ''; 305 | }) 306 | this.make(); 307 | }, 308 | save(){ 309 | const {canvas} = this.$refs; 310 | this.output = canvas.toDataURL('image/jpeg',.95); 311 | this.downloadFilename = `[lab.magiconch.com][福音戰士標題生成器]-${+Date.now()}.jpg`; 312 | }, 313 | tc(){ 314 | this.texts = this.texts.map(s=>{ 315 | if(s.constructor === String) return transformFunc[2](s); 316 | 317 | return s 318 | }); 319 | this.make(); 320 | } 321 | }, 322 | computed:{ 323 | haveMatisse(){ 324 | return haveMatisse 325 | }, 326 | _text(){ 327 | return this.layout.inputs.map((input,index)=>{ 328 | const {type} = input; 329 | if(type==='tab'){ 330 | return this.texts[index]; 331 | } 332 | return textFilter(this.texts[index] || input.placeholder) 333 | }); 334 | }, 335 | allText(){ 336 | return this._text.join(','); 337 | }, 338 | canTc(){ 339 | return this.texts.join() !== transformFunc[2](this.texts.join()) 340 | }, 341 | noMatchMojis(){ 342 | return Array.from(new Set(this.allText)).sort().filter(m=>!EVAMatisseClassicMojis.includes(m)) 343 | } 344 | }, 345 | watch:{ 346 | config:{ 347 | deep:true, 348 | handler:'make' 349 | }, 350 | output(v){ 351 | document.documentElement.setAttribute('data-output',!!v); 352 | }, 353 | // layout:'make', 354 | // texts:{ 355 | // deep:true, 356 | // handler:'make' 357 | // }, 358 | } 359 | }) 360 | 361 | 362 | 363 | 364 | const getQuerys = _=>{ 365 | const GET = {}; 366 | let queryString = location.search.slice(1); 367 | if(queryString){ 368 | let gets = queryString.split(/&/g); 369 | gets.forEach(get=>{ 370 | let [k,v] = get.split(/=/); 371 | GET[decodeURIComponent(k)] = decodeURIComponent(v); 372 | }) 373 | }; 374 | return GET 375 | }; 376 | 377 | let outputCanvas = createCanvas(); 378 | let canvas = createCanvas(); 379 | 380 | const c = async callback=>{ 381 | loadFont('notdef','NotDefault.woff2',async _=>{ 382 | loadFont('baseSplit','base-split.woff?r=220716',async _=>{ 383 | getFontFromText(fontFamilyName,getMoji(),async _=>{ 384 | layouts.slice().sort(_=>-1).forEach((layout,index)=>{ 385 | let texts = [ 386 | // '使徒', 387 | // '襲来', 388 | // '第壱話', 389 | ]; 390 | texts = layout.inputs.map((input,index)=>{ 391 | return texts[index] || input.placeholder 392 | }) 393 | const height = 240; 394 | const config = Object.assign({},defaultConfig,layout.config,{ 395 | height, 396 | // convolute: true, 397 | noise:false, 398 | blur:1, 399 | // inverse: Math.random()>0.9, 400 | }); 401 | make({ 402 | outputCanvas, 403 | canvas, 404 | texts, 405 | config, 406 | layout 407 | }) 408 | const src = makeBMPFormCanvas(outputCanvas) 409 | layout.src = src; 410 | // console.log(src) 411 | // app.$set(layout,'src',src) 412 | // outputEl.appendChild(el) 413 | 414 | // if(layout.exemples){ 415 | // layout.exemples.forEach(texts=>{ 416 | // const el = make({ 417 | // texts, 418 | // config, 419 | // layout 420 | // }) 421 | // // outputEl.appendChild(el) 422 | // }) 423 | // } 424 | }) 425 | app.layouts = layouts; 426 | callback() 427 | }) 428 | }) 429 | }) 430 | } 431 | 432 | c(_=>{ 433 | const GET = getQuerys(); 434 | const layoutId = GET['layout'] || 'e1'; 435 | if(Layouts[layoutId]){ 436 | app.setLayout(Layouts[layoutId],1); 437 | } 438 | 439 | app.loading = false; 440 | }); 441 | 442 | 443 | 444 | -------------------------------------------------------------------------------- /html/document.less: -------------------------------------------------------------------------------- 1 | // @font-face { 2 | // font-family: EVAMatisseClassic; 3 | // src: url(../fonts/EVA-Matisse_Classic.woff2); 4 | // } 5 | // @font-face { 6 | // font-family: MatisseProEB; 7 | // src: url(FOT-MatissePro-EB.woff2); 8 | // } 9 | 10 | // @font-face { 11 | // font-family: NotoSerifSCBlack; 12 | // src: url(NotoSerifSC-Black.woff2); 13 | // } 14 | 15 | 16 | /* 17 | 18 | 19 | "MS Pゴシック","MS P明朝", 20 | "ヒラギノ明朝","游明朝", "Yu Mincho", "游明朝体", YuMincho, 21 | "微軟正黑體", 22 | "ヒラギノ明朝 Pro","PT Serif", 23 | */ 24 | 25 | // @font-face { 26 | // font-family: baseSplit; 27 | // src: url(base-split.woff); 28 | // } 29 | 30 | :root{ 31 | --color-red: #a42121; 32 | --color-background: #252525; 33 | --color-font: #EEE; 34 | } 35 | 36 | html{ 37 | font:24px/1.4 sans-serif; 38 | // font-family: MatisseProEB,serif; 39 | // font-family: EVAMatisseClassic,serif; 40 | // font-family: EVAMatisseClassic,MatisseProEB,serif; 41 | // font-family: NotoSerifSCBlack,serif; 42 | // font-family: MatisseProEB,NotoSerifSCBlack,serif; 43 | // font-weight: 200; 44 | // font-display: swap; 45 | text-rendering: optimizeLegibility; 46 | -webkit-font-smoothing: grayscale; 47 | 48 | background: var(--color-background); 49 | color:var(--color-font); 50 | 51 | // https://codepen.io/mr21/pen/WgyYWM 52 | background-position: top left; 53 | background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23222222' viewBox='0 0 200 339'%3E%3Cpath d='M100,69.6l87,50v100l-87,50l-87-50v-100L100,69.6zM87-18v68L0,100L-18,0M380,0l-180,100L113,50V-18M0,239l87,50V400H-198M400,400H113v-111l87-50'/%3E%3C/svg%3E"); 54 | background-size: 16px; 55 | 56 | &[data-output="true"]{ 57 | overflow: hidden; 58 | } 59 | } 60 | body{ 61 | margin:0; 62 | padding:20px 0 100px; 63 | } 64 | input, 65 | textarea, 66 | button{ 67 | font: inherit 68 | } 69 | 70 | a{ 71 | text-decoration: none; 72 | color:var(--color-red); 73 | } 74 | 75 | h1,h2,h3,h4{ 76 | margin:0; 77 | } 78 | header{ 79 | padding:40px 20px 20px; 80 | line-height: 1; 81 | h1{ 82 | font-weight: 800; 83 | font-size:48px; 84 | font-family: serif; 85 | width: 428px; 86 | max-width:100%; 87 | height:48px; 88 | white-space: nowrap; 89 | overflow: hidden; 90 | color: transparent; 91 | background: url(logo@2x.png) no-repeat center center; 92 | background-size: contain; 93 | } 94 | h2{ 95 | font-weight: 400; 96 | font-family: 'Times New Roman', Times, serif; 97 | padding-top: .5em; 98 | } 99 | } 100 | // h1,h2,h3,h4, 101 | // .exemple-item{ 102 | // font-family: baseSplit,serif; 103 | // } 104 | h3.tip{ 105 | padding:20px; 106 | font-size:14px; 107 | color:var(--color-red); 108 | } 109 | p.tip{ 110 | margin:0; 111 | padding:10px 0; 112 | font-size:14px; 113 | line-height: 28px; 114 | color:var(--color-red); 115 | a{ 116 | font-weight: bold; 117 | text-decoration: underline; 118 | } 119 | } 120 | .layout-list{ 121 | overflow: hidden; 122 | position: relative; 123 | z-index:0; 124 | width:calc(162px * 4); 125 | .item{ 126 | float: left; 127 | cursor: pointer; 128 | padding:1px; 129 | 130 | width: 160px; 131 | height: 120px; 132 | 133 | img{ 134 | display: block; 135 | width: 160px; 136 | height: 120px; 137 | pointer-events: none; 138 | } 139 | position: relative; 140 | b{ 141 | position: absolute; 142 | bottom:0; 143 | left:0; 144 | right:0; 145 | } 146 | &[data-checked]{ 147 | background:var(--color-red); 148 | box-shadow:0 0 0 1px var(--color-red); 149 | z-index:1; 150 | } 151 | } 152 | } 153 | 154 | 155 | 156 | 157 | .ui-switch-box { 158 | cursor: pointer; 159 | margin: .5em; 160 | 161 | display: inline-block; 162 | vertical-align: middle; 163 | 164 | .switch { 165 | display: inline-block; 166 | vertical-align: middle; 167 | 168 | margin-top: -.3em; 169 | width: 2.4em; 170 | height: 1.4em; 171 | border-radius: 9em; 172 | background: rgba(0,0,0,.2); 173 | background: #000; 174 | transition: background-color .3s ease; 175 | .slider { 176 | display: inline-block; 177 | vertical-align: top; 178 | width: 1em; 179 | height: 1em; 180 | border-radius: 9em; 181 | background: var(--color-font); 182 | margin: .2em; 183 | position: relative; 184 | transition: transform .3s ease; 185 | } 186 | } 187 | &[data-checked]{ 188 | .switch { 189 | background: currentColor; 190 | .slider { 191 | transform: translateX(1em) 192 | } 193 | } 194 | } 195 | } 196 | 197 | 198 | 199 | [data-text]{ 200 | &:before{ 201 | content:attr(data-text); 202 | } 203 | } 204 | 205 | .ui-tabs-box{ 206 | display: inline-block; 207 | // margin:10px 0; 208 | border-radius:3px; 209 | overflow: hidden; 210 | text-align: center; 211 | 212 | a{ 213 | float: left; 214 | cursor: pointer; 215 | line-height: 1; 216 | padding:8px; 217 | transition: color .3s ease, background-color .3s ease; 218 | background: var(--color-font); 219 | &[data-checked="true"]{ 220 | background: currentColor; 221 | &:before{ 222 | color: var(--color-font); 223 | } 224 | } 225 | } 226 | } 227 | 228 | section{ 229 | padding:20px; 230 | h2{ 231 | padding-bottom:10px; 232 | } 233 | h3{ 234 | padding-top:.5em; 235 | font-size: 1.2em; 236 | } 237 | h4{ 238 | 239 | } 240 | } 241 | footer{ 242 | padding:100px 0; 243 | font-size:.8em; 244 | h2{ 245 | padding:0; 246 | } 247 | } 248 | 249 | 250 | .output-box{ 251 | canvas{ 252 | max-width: 100%; 253 | } 254 | } 255 | 256 | .config-item{ 257 | padding:10px 0; 258 | } 259 | 260 | .exemple-list{ 261 | overflow: hidden; 262 | padding:10px 0 0; 263 | } 264 | .exemple-item{ 265 | float: left; 266 | cursor: pointer; 267 | background:rgba(0,0,0,.2); 268 | padding:20px 30px; 269 | border-radius:4px; 270 | margin:0 10px 10px 0; 271 | span{ 272 | display: block; 273 | } 274 | &[data-checked="true"]{ 275 | background: var(--color-red); 276 | } 277 | } 278 | 279 | .input-item{ 280 | h4{ 281 | margin:0; 282 | } 283 | } 284 | 285 | .ctrl-box{ 286 | padding:15px 0; 287 | } 288 | 289 | .btn{ 290 | display: inline-block; 291 | background-color: #333; 292 | color:#FFF; 293 | border:0; 294 | font-weight: bold; 295 | line-height: 3; 296 | padding:0 1em; 297 | cursor: pointer; 298 | 299 | 300 | border-radius:4px; 301 | 302 | &.current{ 303 | background-color: var(--color-red); 304 | } 305 | &:disabled{ 306 | background-color: #2D2d2d; 307 | color:#666; 308 | cursor: not-allowed; 309 | } 310 | &.min{ 311 | font-size:.8em; 312 | line-height: 2.6; 313 | padding:0 .8em; 314 | } 315 | } 316 | 317 | .ui-footer{ 318 | // background:rgba(0,0,0,.5); 319 | padding:15px; 320 | a{ 321 | display: inline-block; 322 | padding:.5em .8em; 323 | } 324 | } 325 | @media (min-width:1100px){ 326 | .content-box{ 327 | overflow: hidden; 328 | position: relative; 329 | padding-left:660px; 330 | min-height:480px; 331 | .output-box{ 332 | width: 640px; 333 | position: absolute; 334 | top:0; 335 | left:0; 336 | } 337 | .config-box{ 338 | 339 | } 340 | } 341 | } 342 | 343 | @media (max-width:600px){ 344 | html{ 345 | font-size:18px; 346 | } 347 | section, 348 | header{ 349 | padding:15px; 350 | } 351 | header{ 352 | font-size:.9em; 353 | padding:40px 15px 15px; 354 | } 355 | .layout-list{ 356 | white-space: nowrap; 357 | overflow: auto; 358 | font-size: 0; 359 | width:auto; 360 | .item{ 361 | float: none; 362 | display: inline-block; 363 | } 364 | } 365 | .output-box{ 366 | padding:0; 367 | h2{ 368 | padding:15px; 369 | } 370 | canvas{ 371 | display: block; 372 | height:auto !important; 373 | } 374 | } 375 | } 376 | 377 | input { 378 | border: 2px solid currentColor; 379 | margin: 0.4em 0; 380 | padding: 0.3em 0.5em; 381 | width: 600px; 382 | box-sizing: border-box; 383 | max-width: 100%; 384 | outline: none; 385 | line-height: 1; 386 | &:focus{ 387 | border-color:var(--color-red); 388 | } 389 | } 390 | 391 | 392 | 393 | .app{ 394 | &:before{ 395 | position: fixed; 396 | top:0; 397 | right:0; 398 | content: '生成中...'; 399 | background: #111; 400 | color:var(--color-red); 401 | padding:.5em .8em; 402 | margin:.8em; 403 | border-radius:4px; 404 | pointer-events: none; 405 | opacity: 0; 406 | } 407 | &[data-loading="true"]{ 408 | cursor: wait; 409 | &>*{ 410 | pointer-events: none; 411 | } 412 | &:before{ 413 | opacity: 1; 414 | } 415 | } 416 | } 417 | 418 | 419 | 420 | .canvas-box{ 421 | position: fixed; 422 | top: 0; 423 | right: 0; 424 | width: 1px; 425 | height: 1px; 426 | overflow: hidden; 427 | } 428 | 429 | 430 | 431 | 432 | .ui-shadow{ 433 | position: fixed; 434 | top:0; 435 | right:0; 436 | left:0; 437 | bottom:0; 438 | z-index:1; 439 | background:rgba(0,0,0,.5); 440 | overflow: auto; 441 | overflow-y: scroll; 442 | overscroll-behavior: contain; 443 | } 444 | .ui-output-box{ 445 | text-align: center; 446 | font-size:14px; 447 | background:#222; 448 | width: 800px; 449 | max-width:100%; 450 | margin:40px auto; 451 | padding:20px; 452 | box-sizing: border-box; 453 | box-shadow: 0 0 40px rgba(50,10,10,.4); 454 | h2{ 455 | padding-bottom:10px; 456 | } 457 | img{ 458 | display: block; 459 | width:800px; 460 | max-width:100%; 461 | box-shadow: 0 0 20px rgba(0, 0, 0, 0.2); 462 | margin:10px 0 40px; 463 | } 464 | .ctrl-box{ 465 | padding:10px 0; 466 | } 467 | } -------------------------------------------------------------------------------- /html/eva-matisse-classic-moji-list.js: -------------------------------------------------------------------------------- 1 | let EVAMatisseClassicMojis = `  !"#$%&'()*+,-‑./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[¥]^_̲\`̀abcdefghijklmnopqrstuvwxyz{¦}˜̃ʼ’\ʻ‘|~∼¡¢£⁄ƒ¤“«‹›fifl‒–·∙•‚„”»¿́ˆ̂¯̄̆̇̈˚̊¸̶̧̨̋̌—ÆªŁØŒºæıłøœß­©¬®²³µ¹¼½¾ÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖÙÚÛÜÝÞàáâãäåçèéêëìíîïðñòóôõöùúûüýþÿŠŸŽ̅‾š™ž │。「」、・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン゙゚  、。,.・:;゛゜´`¨^ ̄_ヽヾゝゞ〃仝々〆〇ー―‐/\〜~‖∥|…‥()〔〕[]{}〈〉《》「」『』【】+−-±×÷=≠<>≦≧∞∴♂♀°′″℃¥$¢£%#&*@§☆★○●◎◇◆□■△▲▽▼※〒→←↑↓〓∈∋⊆⊇⊂⊃∪∩∧∨¬⇒⇔∀∃∠⊥⌒∂∇≡≒≪≫√∽∝∵∫∬ʼn♯♭♪†‡¶◯0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをんァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρστυφχψωАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя亜唖娃阿哀愛挨姶逢葵茜穐悪握渥旭葦芦鯵梓圧斡扱宛姐虻飴絢綾鮎或粟袷安庵按暗案闇鞍杏以伊位依偉囲夷委威尉惟意慰易椅為畏異移維緯胃萎⾐衣謂違遺医井亥域育郁磯⼀一壱溢逸稲茨芋鰯允印咽員因姻引飲淫胤蔭院陰隠韻吋右宇烏⽻羽迂⾬雨卯鵜窺丑碓⾅臼渦嘘唄欝蔚鰻姥厩浦⽠瓜閏噂云運雲荏餌叡営嬰影映曳栄永泳洩瑛盈穎頴英衛詠鋭液疫益駅悦謁越閲榎厭円園堰奄宴延怨掩援沿演炎焔煙燕猿縁艶苑薗遠鉛鴛塩於汚甥凹央奥往応押旺横欧殴王翁襖鴬鴎⻩黄岡沖荻億屋憶臆桶牡⼄乙俺卸恩温穏⾳音下化仮何伽価佳加可嘉夏嫁家寡科暇果架歌河⽕火珂禍⽲禾稼箇花苛茄荷華菓蝦課嘩貨迦過霞蚊俄峨我⽛牙画臥芽蛾賀雅餓駕介会解回塊壊廻快怪悔恢懐戒拐改魁晦械海灰界皆絵芥蟹開階⾙貝凱劾外咳害崖慨概涯碍蓋街該鎧骸浬馨蛙垣柿蛎鈎劃嚇各廓拡撹格核殻獲確穫覚⾓角赫較郭閣隔⾰革学岳楽額顎掛笠樫橿梶鰍潟割喝恰括活渇滑葛褐轄且鰹叶椛樺鞄株兜竃蒲釜鎌噛鴨栢茅萱粥刈苅⽡瓦乾侃冠寒刊勘勧巻喚堪姦完官寛⼲干幹患感慣憾換敢柑桓棺款歓汗漢澗潅環⽢甘監看竿管簡緩⽸缶翰肝艦莞観諌貫還鑑間閑関陥韓館舘丸含岸巌玩癌眼岩翫贋雁頑顔願企伎危喜器基奇嬉寄岐希幾忌揮机旗既期棋棄機帰毅気汽畿祈季稀紀徽規記貴起軌輝飢騎⻤⿁鬼⻲亀偽儀妓宜戯技擬欺犠疑祇義蟻誼議掬菊鞠吉吃喫桔橘詰砧杵⿉黍却客脚虐逆丘久仇休及吸宮⼸弓急救朽求汲泣灸球究窮笈級糾給旧⽜牛去居巨拒拠挙渠虚許距鋸漁禦⿂魚亨享京供侠僑兇競共凶協匡卿叫喬境峡強彊怯恐恭挟教橋況狂狭矯胸脅興蕎郷鏡響饗驚仰凝尭暁業局曲極⽟玉桐粁僅勤均⼱巾錦⽄斤欣欽琴禁禽筋緊芹菌衿襟謹近⾦金吟銀九倶句区狗玖矩苦躯駆駈駒具愚虞喰空偶寓遇隅串櫛釧屑屈掘窟沓靴轡窪熊隈粂栗繰桑鍬勲君薫訓群軍郡卦袈祁係傾刑兄啓圭珪型契形径恵慶慧憩掲携敬景桂渓畦稽系経継繋罫茎荊蛍計詣警軽頚鶏芸迎鯨劇戟撃激隙桁傑⽋欠決潔⽳穴結⾎血訣⽉月件倹倦健兼券剣喧圏堅嫌建憲懸拳捲検権牽⽝犬献研硯絹県肩⾒見謙賢軒遣鍵険顕験鹸元原厳幻弦減源⽞玄現絃舷⾔言諺限乎個古呼固姑孤⼰己庫弧⼾戸故枯湖狐糊袴股胡菰⻁虎誇跨鈷雇顧⿎鼓五互伍午呉吾娯後御悟梧檎瑚碁語誤護醐乞鯉交佼侯候倖光公功効勾厚⼝口向后喉坑垢好孔孝宏⼯工巧巷幸広庚康弘恒慌抗拘控攻昂晃更杭校梗構江洪浩港溝甲皇硬稿糠紅紘絞綱耕考肯肱腔膏航荒⾏行衡講貢購郊酵鉱砿鋼閤降項⾹香⾼高鴻剛劫号合壕拷濠豪轟麹克刻告国穀酷鵠⿊黒獄漉腰甑忽惚⾻骨狛込此頃今困坤墾婚恨懇昏昆根梱混痕紺⾉艮魂些佐叉唆嵯左差査沙瑳砂詐鎖裟坐座挫債催再最哉塞妻宰彩才採栽歳済災采犀砕砦祭斎細菜裁載際剤在材罪財冴坂阪堺榊肴咲崎埼碕鷺作削咋搾昨朔柵窄策索錯桜鮭笹匙冊刷察拶撮擦札殺薩雑皐鯖捌錆鮫⽫皿晒三傘参⼭山惨撒散桟燦珊産算纂蚕讃賛酸餐斬暫残仕仔伺使刺司史嗣四⼠士始姉姿⼦子屍市師志思指⽀支孜斯施旨枝⽌止死⽒氏獅祉私⽷糸紙紫肢脂⾄至視詞詩試誌諮資賜雌飼⻭歯事似侍児字寺慈持時次滋治爾璽痔磁⽰示⽽而⽿耳⾃自蒔辞汐⿅鹿式識鴫竺軸宍雫七叱執失嫉室悉湿漆疾質実蔀篠偲柴芝屡蕊縞舎写射捨赦斜煮社紗者謝⾞車遮蛇邪借勺尺杓灼爵酌釈錫若寂弱惹主取守⼿手朱殊狩珠種腫趣酒⾸首儒受呪寿授樹綬需囚収周宗就州修愁拾洲秀秋終繍習臭⾈舟蒐衆襲讐蹴輯週酋酬集醜什住充⼗十従戎柔汁渋獣縦重銃叔夙宿淑祝縮粛塾熟出術述俊峻春瞬竣舜駿准循旬楯殉淳準潤盾純巡遵醇順処初所暑曙渚庶緒署書薯藷諸助叙⼥女序徐恕鋤除傷償勝匠升召哨商唱嘗奨妾娼宵将⼩小少尚庄床廠彰承抄招掌捷昇昌昭晶松梢樟樵沼消渉湘焼焦照症省硝礁祥称章笑粧紹肖菖蒋蕉衝裳訟証詔詳象賞醤鉦鍾鐘障鞘上丈丞乗冗剰城場壌嬢常情擾条杖浄状畳穣蒸譲醸錠嘱埴飾拭植殖燭織職⾊色触⾷食蝕辱尻伸信侵唇娠寝審⼼心慎振新晋森榛浸深申疹真神秦紳⾂臣芯薪親診⾝身⾟辛進針震⼈人仁刃塵壬尋甚尽腎訊迅陣靭笥諏須酢図厨逗吹垂帥推⽔水炊睡粋翠衰遂酔錐錘随瑞髄崇嵩数枢趨雛据杉椙菅頗雀裾澄摺⼨寸世瀬畝是凄制勢姓征性成政整星晴棲栖正清牲⽣生盛精聖声製⻄西誠誓請逝醒⻘青静⻫斉税脆隻席惜戚斥昔析⽯石積籍績脊責⾚赤跡蹟碩切拙接摂折設窃節説雪絶⾆舌蝉仙先千占宣専尖川戦扇撰栓栴泉浅洗染潜煎煽旋穿箭線繊羨腺⾇舛船薦詮賎践選遷銭銑閃鮮前善漸然全禅繕膳糎噌塑岨措曾曽楚狙疏疎礎祖租粗素組蘇訴阻遡⿏鼠僧創双叢倉喪壮奏爽宋層匝惣想捜掃挿掻操早曹巣槍槽漕燥争痩相窓糟総綜聡草荘葬蒼藻装⾛走送遭鎗霜騒像増憎臓蔵贈造促側則即息捉束測⾜足速俗属賊族続卒袖其揃存孫尊損村遜他多太汰詑唾堕妥惰打柁舵楕陀駄騨体堆対耐岱帯待怠態戴替泰滞胎腿苔袋貸退逮隊黛鯛代台⼤大第醍題鷹滝瀧卓啄宅托択拓沢濯琢託鐸濁諾茸凧蛸只叩但達⾠辰奪脱巽竪辿棚⾕谷狸鱈樽誰丹単嘆坦担探旦歎淡湛炭短端箪綻耽胆蛋誕鍛団壇弾断暖檀段男談値知地弛恥智池痴稚置致蜘遅馳築畜⽵竹筑蓄逐秩窒茶嫡着中仲宙忠抽昼柱注⾍虫衷註酎鋳駐樗瀦猪苧著貯丁兆凋喋寵帖帳庁弔張彫徴懲挑暢朝潮牒町眺聴脹腸蝶調諜超跳銚⻑⾧長頂⿃鳥勅捗直朕沈珍賃鎮陳津墜椎槌追鎚痛通塚栂掴槻佃漬柘辻蔦綴鍔椿潰坪壷嬬紬⽖爪吊釣鶴亭低停偵剃貞呈堤定帝底庭廷弟悌抵挺提梯汀碇禎程締艇訂諦蹄逓邸鄭釘⿍鼎泥摘擢敵滴的笛適鏑溺哲徹撤轍迭鉄典填天展店添纏甜貼転顛点伝殿澱⽥田電兎吐堵塗妬屠徒⽃斗杜渡登菟賭途都鍍砥砺努度⼟土奴怒倒党冬凍⼑刀唐塔塘套宕島嶋悼投搭東桃梼棟盗淘湯涛灯燈当痘祷等答筒糖統到董蕩藤討謄⾖豆踏逃透鐙陶頭騰闘働動同堂導憧撞洞瞳童胴萄道銅峠鴇匿得徳涜特督禿篤毒独読栃橡凸突椴届鳶苫寅⾣酉瀞噸屯惇敦沌豚遁頓呑曇鈍奈那内乍凪薙謎灘捺鍋楢馴縄畷南楠軟難汝⼆二尼弐迩匂賑⾁肉虹廿⽇日乳⼊入如尿韮任妊忍認濡禰祢寧葱猫熱年念捻撚燃粘乃廼之埜嚢悩濃納能脳膿農覗蚤巴把播覇杷波派琶破婆罵芭⾺馬俳廃拝排敗杯盃牌背肺輩配倍培媒梅楳煤狽買売賠陪這蝿秤矧萩伯剥博拍柏泊⽩白箔粕舶薄迫曝漠爆縛莫駁⻨麦函箱硲箸肇筈櫨幡肌畑畠⼋八鉢溌発醗髪伐罰抜筏閥鳩噺塙蛤隼伴判半反叛帆搬斑板氾汎版犯班畔繁般藩販範⾤釆煩頒飯挽晩番盤磐蕃蛮匪卑否妃庇彼悲扉批披斐⽐比泌疲⽪皮碑秘緋罷肥被誹費避⾮非⾶飛樋簸備尾微枇毘琵眉美⿐鼻柊稗匹⽦疋髭彦膝菱肘弼必畢筆逼桧姫媛紐百謬俵彪標氷漂瓢票表評豹廟描病秒苗錨鋲蒜蛭鰭品彬斌浜瀕貧賓頻敏瓶不付埠夫婦富冨布府怖扶敷斧普浮⽗父符腐膚芙譜負賦赴⾩阜附侮撫武舞葡蕪部封楓⾵風葺蕗伏副復幅服福腹複覆淵弗払沸仏物鮒分吻噴墳憤扮焚奮粉糞紛雰⽂文聞丙併兵塀幣平弊柄並蔽閉陛⽶米⾴頁僻壁癖碧別瞥蔑箆偏変⽚片篇編辺返遍便勉娩弁鞭保舗鋪圃捕歩甫補輔穂募墓慕戊暮⺟母簿菩倣俸包呆報奉宝峰峯崩庖抱捧放⽅方朋法泡烹砲縫胞芳萌蓬蜂褒訪豊邦鋒飽鳳鵬乏亡傍剖坊妨帽忘忙房暴望某棒冒紡肪膨謀貌貿鉾防吠頬北僕⼘卜墨撲朴牧睦穆釦勃没殆堀幌奔本翻凡盆摩磨魔⿇麻埋妹昧枚毎哩槙幕膜枕鮪柾鱒桝亦俣⼜又抹末沫迄侭繭麿万慢満漫蔓味未魅⺒巳箕岬密蜜湊蓑稔脈妙粍⺠民眠務夢無牟⽭矛霧鵡椋婿娘冥名命明盟迷銘鳴姪牝滅免棉綿緬⾯面麺摸模茂妄孟⽑毛猛盲網耗蒙儲⽊木黙⽬目杢勿餅尤戻籾貰問悶紋⾨門匁也冶夜爺耶野弥⽮矢厄役約薬訳躍靖柳薮鑓愉愈油癒諭輸唯佑優勇友宥幽悠憂揖有柚湧涌猶猷由祐裕誘遊⾢邑郵雄融⼣夕予余与誉輿預傭幼妖容庸揚揺擁曜楊様洋溶熔⽤用窯⽺羊耀葉蓉要謡踊遥陽養慾抑欲沃浴翌翼淀羅螺裸来莱頼雷洛絡落酪乱卵嵐欄濫藍蘭覧利吏履李梨理璃痢裏裡⾥里離陸律率⽴立葎掠略劉流溜琉留硫粒隆⻯竜⿓龍侶慮旅虜了亮僚両凌寮料梁涼猟療瞭稜糧良諒遼量陵領⼒力緑倫厘林淋燐琳臨輪隣鱗麟瑠塁涙累類令伶例冷励嶺怜玲礼苓鈴隷零霊麗齢暦歴列劣烈裂廉恋憐漣煉簾練聯蓮連錬呂魯櫓炉賂路露労婁廊弄朗楼榔浪漏牢狼篭⽼老聾蝋郎六麓禄肋録論倭和話歪賄脇惑枠鷲亙亘鰐詫藁蕨椀湾碗腕弌丐丕个丱⼂丶丼⼃丿乂乖乘亂⼅亅豫亊舒弍于亞亟⼇亠亢亰亳亶从仍仄仆仂仗仞仭仟价伉佚估佛佝佗佇佶侈侏侘佻佩佰侑佯來侖儘俔俟俎俘俛俑俚俐俤俥倚倨倔倪倥倅伜俶倡倩倬俾俯們倆偃假會偕偐偈做偖偬偸傀傚傅傴傲僉僊傳僂僖僞僥僭僣僮價僵儉儁儂儖儕儔儚儡儺儷儼儻⼉儿⺎兀兒兌兔兢竸兩兪兮冀⼌冂囘册冉冏冑冓冕⼍冖冤冦冢冩冪⼎冫决冱冲冰况冽凅凉凛⼏几處凩凭凰⼐凵凾刄刋刔刎刧刪刮刳刹剏剄剋剌剞剔剪剴剩剳剿剽劍劔劒剱劈劑辨辧劬劭劼劵勁勍勗勞勣勦飭勠勳勵勸⼓勹匆匈甸匍匐匏⼔匕⼕匚匣匯匱匳⼖匸區卆卅丗卉卍凖卞⼙卩卮夘卻卷⼚厂厖厠厦厥厮厰⼛厶參簒雙叟曼燮叮叨叭叺吁吽呀听吭吼吮吶吩吝呎咏呵咎呟呱呷呰咒呻咀呶咄咐咆哇咢咸咥咬哄哈咨咫哂咤咾咼哘哥哦唏唔哽哮哭哺哢唹啀啣啌售啜啅啖啗唸唳啝喙喀咯喊喟啻啾喘喞單啼喃喩喇喨嗚嗅嗟嗄嗜嗤嗔嘔嗷嘖嗾嗽嘛嗹噎噐營嘴嘶嘲嘸噫噤嘯噬噪嚆嚀嚊嚠嚔嚏嚥嚮嚶嚴囂嚼囁囃囀囈囎囑囓⼞囗囮囹圀囿圄圉圈國圍圓團圖嗇圜圦圷圸坎圻址坏坩埀垈坡坿垉垓垠垳垤垪垰埃埆埔埒埓堊埖埣堋堙堝塲堡塢塋塰毀塒堽塹墅墹墟墫墺壞墻墸墮壅壓壑壗壙壘壥壜壤壟壯壺壹壻壼壽⼡夂⼢夊夐夛梦夥夬夭夲夸夾竒奕奐奎奚奘奢奠奧奬奩奸妁妝佞侫妣妲姆姨姜妍姙姚娥娟娑娜娉娚婀婬婉娵娶婢婪媚媼媾嫋嫂媽嫣嫗嫦嫩嫖嫺嫻嬌嬋嬖嬲嫐嬪嬶嬾孃孅孀孑孕孚孛孥孩孰孳孵學斈孺⼧宀它宦宸寃寇寉寔寐寤實寢寞寥寫寰寶寳尅將專對尓尠⺐⼪尢尨⼫尸尹屁屆屎屓屐屏孱屬⼬屮乢屶屹岌岑岔妛岫岻岶岼岷峅岾峇峙峩峽峺峭嶌峪崋崕崗嵜崟崛崑崔崢崚崙崘嵌嵒嵎嵋嵬嵳嵶嶇嶄嶂嶢嶝嶬嶮嶽嶐嶷嶼巉巍巓巒巖⼮巛巫已巵帋帚帙帑帛帶帷幄幃幀幎幗幔幟幢幤幇幵并⺓⼳幺麼⼴广庠廁廂廈廐廏廖廣廝廚廛廢廡廨廩廬廱廳廰⼵廴廸⼶廾弃弉彝彜⼷弋弑弖弩弭弸彁彈彌彎弯⺔彑彖彗彙⼺彡彭⼻彳彷徃徂彿徊很徑徇從徙徘徠徨徭徼忖忻忤忸忱忝悳忿怡恠怙怐怩怎怱怛怕怫怦怏怺恚恁恪恷恟恊恆恍恣恃恤恂恬恫恙悁悍惧悃悚悄悛悖悗悒悧悋惡悸惠惓悴忰悽惆悵惘慍愕愆惶惷愀惴惺愃愡惻惱愍愎慇愾愨愧慊愿愼愬愴愽慂慄慳慷慘慙慚慫慴慯慥慱慟慝慓慵憙憖憇憬憔憚憊憑憫憮懌懊應懷懈懃懆憺懋罹懍懦懣懶懺懴懿懽懼懾戀⼽戈戉戍戌戔戛戞戡截戮戰戲戳扁扎扞扣扛扠扨扼抂抉找抒抓抖拔抃抔拗拑抻拏拿拆擔拈拜拌拊拂拇抛拉挌拮拱挧挂挈拯拵捐挾捍搜捏掖掎掀掫捶掣掏掉掟掵捫捩掾揩揀揆揣揉插揶揄搖搴搆搓搦搶攝搗搨搏摧摯摶摎攪撕撓撥撩撈撼據擒擅擇撻擘擂擱擧舉擠擡抬擣擯攬擶擴擲擺攀擽攘攜攅攤攣攫⽁攴⺙攵攷收攸畋效敖敕敍敘敞敝敲數斂斃變斛斟斫斷旃旆旁旄旌旒旛旙⽆无⺛旡旱杲昊昃旻杳昵昶昴昜晏晄晉晁晞晝晤晧晨晟晢晰暃暈暎暉暄暘暝曁暹曉暾暼曄暸曖曚曠昿曦曩⽈曰曵曷朏朖朞朦朧霸朮朿朶杁朸朷杆杞杠杙杣杤枉杰枩杼杪枌枋枦枡枅枷柯枴柬枳柩枸柤柞柝柢柮枹柎柆柧檜栞框栩桀桍栲桎梳栫桙档桷桿梟梏梭梔條梛梃檮梹桴梵梠梺椏梍桾椁棊椈棘椢椦棡椌棍棔棧棕椶椒椄棗棣椥棹棠棯椨椪椚椣椡棆楹楷楜楸楫楔楾楮椹楴椽楙椰楡楞楝榁楪榲榮槐榿槁槓榾槎寨槊槝榻槃榧樮榑榠榜榕榴槞槨樂樛槿權槹槲槧樅榱樞槭樔槫樊樒櫁樣樓橄樌橲樶橸橇橢橙橦橈樸樢檐檍檠檄檢檣檗蘗檻櫃櫂檸檳檬櫞櫑櫟檪櫚櫪櫻欅蘖櫺欒欖鬱欟欸欷盜欹飮歇歃歉歐歙歔歛歟歡歸⽍歹歿殀殄殃殍殘殕殞殤殪殫殯殲殱⽎殳殷殼毆⽏毋毓毟毬毫毳毯麾氈氓⽓气氛氤氣汞汕汢汪沂沍沚沁沛汾汨汳沒沐泄泱泓沽泗泅泝沮沱沾沺泛泯泙泪洟衍洶洫洽洸洙洵洳洒洌浣涓浤浚浹浙涎涕濤涅淹渕渊涵淇淦涸淆淬淞淌淨淒淅淺淙淤淕淪淮渭湮渮渙湲湟渾渣湫渫湶湍渟湃渺湎渤滿渝游溂溪溘滉溷滓溽溯滄溲滔滕溏溥滂溟潁漑灌滬滸滾漿滲漱滯漲滌漾漓滷澆潺潸澁澀潯潛濳潭澂潼潘澎澑濂潦澳澣澡澤澹濆澪濟濕濬濔濘濱濮濛瀉瀋濺瀑瀁瀏濾瀛瀚潴瀝瀘瀟瀰瀾瀲灑灣炙炒炯烱炬炸炳炮烟烋烝烙焉烽焜焙煥煕熈煦煢煌煖煬熏燻熄熕熨熬燗熹熾燒燉燔燎燠燬燧燵燼燹燿爍爐爛爨爭爬爰爲⽘爻爼⽙爿牀牆牋牘牴牾犂犁犇犒犖犢犧犹犲狃狆狄狎狒狢狠狡狹狷倏猗猊猜猖猝猴猯猩猥猾獎獏默獗獪獨獰獸獵獻獺珈玳珎玻珀珥珮珞璢琅瑯琥珸琲琺瑕琿瑟瑙瑁瑜瑩瑰瑣瑪瑶瑾璋璞璧瓊瓏瓔珱瓠瓣瓧瓩瓮瓲瓰瓱瓸瓷甄甃甅甌甎甍甕甓甞甦甬甼畄畍畊畉畛畆畚畩畤畧畫畭畸當疆疇畴疊疉疂疔疚疝疥疣痂疳痃疵疽疸疼疱痍痊痒痙痣痞痾痿痼瘁痰痺痲痳瘋瘍瘉瘟瘧瘠瘡瘢瘤瘴瘰瘻癇癈癆癜癘癡癢癨癩癪癧癬癰癲⽨癶癸發皀皃皈皋皎皖皓皙皚皰皴皸皹皺盂盍盖盒盞盡盥盧盪蘯盻眈眇眄眩眤眞眥眦眛眷眸睇睚睨睫睛睥睿睾睹瞎瞋瞑瞠瞞瞰瞶瞹瞿瞼瞽瞻矇矍矗矚矜矣矮矼砌砒礦砠礪硅碎硴碆硼碚碌碣碵碪碯磑磆磋磔碾碼磅磊磬磧磚磽磴礇礒礑礙礬礫祀祠祗祟祚祕祓祺祿禊禝禧齋禪禮禳禹禺秉秕秧秬秡秣稈稍稘稙稠稟禀稱稻稾稷穃穗穉穡穢穩龝穰穹穽窈窗窕窘窖窩竈窰窶竅竄窿邃竇竊竍竏竕竓站竚竝竡竢竦竭竰笂笏笊笆笳笘笙笞笵笨笶筐筺笄筍笋筌筅筵筥筴筧筰筱筬筮箝箘箟箍箜箚箋箒箏筝箙篋篁篌篏箴篆篝篩簑簔篦篥籠簀簇簓篳篷簗簍篶簣簧簪簟簷簫簽籌籃籔籏籀籐籘籟籤籖籥籬籵粃粐粤粭粢粫粡粨粳粲粱粮粹粽糀糅糂糘糒糜糢鬻糯糲糴糶糺紆紂紜紕紊絅絋紮紲紿紵絆絳絖絎絲絨絮絏絣經綉絛綏絽綛綺綮綣綵緇綽綫總綢綯緜綸綟綰緘緝緤緞緻緲緡縅縊縣縡縒縱縟縉縋縢繆繦縻縵縹繃縷縲縺繧繝繖繞繙繚繹繪繩繼繻纃緕繽辮繿纈纉續纒纐纓纔纖纎纛纜缸缺罅罌罍罎罐⽹网罕罔罘罟罠罨罩罧罸羂羆羃羈羇羌羔羞羝羚羣羯羲羹羮羶羸譱翅翆翊翕翔翡翦翩翳翹飜耆耄耋⽾耒耘耙耜耡耨耿耻聊聆聒聘聚聟聢聨聳聲聰聶聹聽⾀聿肄肆肅肛肓肚肭冐肬胛胥胙胝胄胚胖脉胯胱脛脩脣脯腋隋腆脾腓腑胼腱腮腥腦腴膃膈膊膀膂膠膕膤膣腟膓膩膰膵膾膸膽臀臂膺臉臍臑臙臘臈臚臟臠臧臺臻臾舁舂舅與舊舍舐舖舩舫舸舳艀艙艘艝艚艟艤艢艨艪艫舮艱艷⾋艸艾芍芒芫芟芻芬苡苣苟苒苴苳苺莓范苻苹苞茆苜茉苙茵茴茖茲茱荀茹荐荅茯茫茗茘莅莚莪莟莢莖茣莎莇莊荼莵荳荵莠莉莨菴萓菫菎菽萃菘萋菁菷萇菠菲萍萢萠莽萸蔆菻葭萪萼蕚蒄葷葫蒭葮蒂葩葆萬葯葹萵蓊葢蒹蒿蒟蓙蓍蒻蓚蓐蓁蓆蓖蒡蔡蓿蓴蔗蔘蔬蔟蔕蔔蓼蕀蕣蕘蕈蕁蘂蕋蕕薀薤薈薑薊薨蕭薔薛藪薇薜蕷蕾薐藉薺藏薹藐藕藝藥藜藹蘊蘓蘋藾藺蘆蘢蘚蘰蘿⾌虍乕虔號虧虱蚓蚣蚩蚪蚋蚌蚶蚯蛄蛆蚰蛉蠣蚫蛔蛞蛩蛬蛟蛛蛯蜒蜆蜈蜀蜃蛻蜑蜉蜍蛹蜊蜴蜿蜷蜻蜥蜩蜚蝠蝟蝸蝌蝎蝴蝗蝨蝮蝙蝓蝣蝪蠅螢螟螂螯蟋螽蟀蟐雖螫蟄螳蟇蟆螻蟯蟲蟠蠏蠍蟾蟶蟷蠎蟒蠑蠖蠕蠢蠡蠱蠶蠹蠧蠻衄衂衒衙衞衢衫袁衾袞衵衽袵衲袂袗袒袮袙袢袍袤袰袿袱裃裄裔裘裙裝裹褂裼裴裨裲褄褌褊褓襃褞褥褪褫襁襄褻褶褸襌褝襠襞襦襤襭襪襯襴襷⾑襾覃覈覊覓覘覡覩覦覬覯覲覺覽覿觀觚觜觝觧觴觸訃訖訐訌訛訝訥訶詁詛詒詆詈詼詭詬詢誅誂誄誨誡誑誥誦誚誣諄諍諂諚諫諳諧諤諱謔諠諢諷諞諛謌謇謚諡謖謐謗謠謳鞫謦謫謾謨譁譌譏譎證譖譛譚譫譟譬譯譴譽讀讌讎讒讓讖讙讚谺豁谿豈豌豎豐⾗豕豢豬⾘豸豺貂貉貅貊貍貎貔豼貘戝貭貪貽貲貳貮貶賈賁賤賣賚賽賺賻贄贅贊贇贏贍贐齎贓賍贔贖赧赭赱赳趁趙跂趾趺跏跚跖跌跛跋跪跫跟跣跼踈踉跿踝踞踐踟蹂踵踰踴蹊蹇蹉蹌蹐蹈蹙蹤蹠踪蹣蹕蹶蹲蹼躁躇躅躄躋躊躓躑躔躙躪躡躬躰軆躱躾軅軈軋軛軣軼軻軫軾輊輅輕輒輙輓輜輟輛輌輦輳輻輹轅轂輾轌轉轆轎轗轜轢轣轤辜辟辣辭辯辷迚迥迢迪迯邇迴逅迹迺逑逕逡逍逞逖逋逧逶逵逹迸遏遐遑遒逎遉逾遖遘遞遨遯遶隨遲邂遽邁邀邊邉邏邨邯邱邵郢郤扈郛鄂鄒鄙鄲鄰酊酖酘酣酥酩酳酲醋醉醂醢醫醯醪醵醴醺釀釁釉釋釐釖釟釡釛釼釵釶鈞釿鈔鈬鈕鈑鉞鉗鉅鉉鉤鉈銕鈿鉋鉐銜銖銓銛鉚鋏銹銷鋩錏鋺鍄錮錙錢錚錣錺錵錻鍜鍠鍼鍮鍖鎰鎬鎭鎔鎹鏖鏗鏨鏥鏘鏃鏝鏐鏈鏤鐚鐔鐓鐃鐇鐐鐶鐫鐵鐡鐺鑁鑒鑄鑛鑠鑢鑞鑪鈩鑰鑵鑷鑽鑚鑼鑾钁鑿閂閇閊閔閖閘閙閠閨閧閭閼閻閹閾闊濶闃闍闌闕闔闖關闡闥闢阡阨阮阯陂陌陏陋陷陜陞陝陟陦陲陬隍隘隕隗險隧隱隲隰隴⾪隶隸⾫隹雎雋雉雍襍雜霍雕雹霄霆霈霓霎霑霏霖霙霤霪霰霹霽霾靄靆靈靂靉靜靠靤靦靨勒靫靱靹鞅靼鞁靺鞆鞋鞏鞐鞜鞨鞦鞣鞳鞴韃韆韈⾱韋韜⾲韭齏韲竟韶韵頏頌頸頤頡頷頽顆顏顋顫顯顰顱顴顳颪颯颱颶飄飃飆飩飫餃餉餒餔餘餡餝餞餤餠餬餮餽餾饂饉饅饐饋饑饒饌饕馗馘馥馭馮馼駟駛駝駘駑駭駮駱駲駻駸騁騏騅駢騙騫騷驅驂驀驃騾驕驍驛驗驟驢驥驤驩驫驪骭骰骼髀髏髑髓體髞⾽髟髢髣髦髯髫髮髴髱髷髻鬆鬘鬚鬟鬢鬣⾾鬥鬧鬨鬩鬪鬮⾿鬯⿀鬲魄魃魏魍魎魑魘魴鮓鮃鮑鮖鮗鮟鮠鮨鮴鯀鯊鮹鯆鯏鯑鯒鯣鯢鯤鯔鯡鰺鯲鯱鯰鰕鰔鰉鰓鰌鰆鰈鰒鰊鰄鰮鰛鰥鰤鰡鰰鱇鰲鱆鰾鱚鱠鱧鱶鱸鳧鳬鳰鴉鴈鳫鴃鴆鴪鴦鶯鴣鴟鵄鴕鴒鵁鴿鴾鵆鵈鵝鵞鵤鵑鵐鵙鵲鶉鶇鶫鵯鵺鶚鶤鶩鶲鷄鷁鶻鶸鶺鷆鷏鷂鷙鷓鷸鷦鷭鷯鷽鸚鸛鸞⿄鹵鹹鹽麁麈麋麌麒麕麑麝⿆麥麩麸麪麭靡黌黎黏黐黔黜點黝黠黥黨黯黴黶黷⿋黹黻黼⿌黽鼇鼈皷鼕鼡鼬鼾⿑齊⿒齒齔齣齟齠齡齦齧齬齪齷齲齶龕⿔龜⿕龠堯槇遙瑤─━│┃┄┅┆┇┈┉┊┋┌┍┎┏┐┑┒┓└┕┖┗┘┙┚┛├┝┞┟┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ㍉㌔㍍㌧㌶㍑㌍㌦㌫㍊㎜㎝㎞㎎㎏㏄㎡〝〟№㏍㊤㊥㊦㊧㊨㈱㈲㈹㍾㍽㍼∮∑∟⊿啞焰鷗嚙㵎俠軀繫鹼昻麴栅屢繡蔣醬蟬搔瘦驒簞摑塡顚禱瀆囊剝潑醱頰麵萊蠟屛攢梎蔾睢︑︒︳︱︲⁝︙⁚︰︵︶︹︺﹇﹈︷︸︿﹀︽︾﹁﹂﹃﹄︻︼ゔゕゖ噓¦'"◁▷⇩⇧⇦⇨▢♧♡♤♢㎠㎢㎤㎥㎗ℓ㎘㎳㎲㎱㎰㎅㎆㎇㏋㎐㏔㎖㌢㌖㌘㌕㌃㌣㍗㍂㌹㌻㌀㌱㍇㌞㌪㍿℡☎〶〠⒈⒉⒊⒋⒌⒍⒎⒏⒐⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂⒃⒄⒅⒆⒇㉑ⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹ㉒㉓㉔㉕㉖㉗㉘㉙㉚㉛⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵㉃㈽㈿㈴㈸㈳㈼㉂㈾㈶㈵㈻㉀㈺㊰㊭㊩㊯㊔㊪㊘㊫㊒㊑㊓㊬㊮㊖⎫⎬⎭⎧⎨⎩㏌㌅㎟㎣㎦㊞㎈㎉㏈∭㈰㈪㈫㈬㈭㈮㈯㈷㉁➡⬅⬆⬇◉♠♥♣♦☀☁☂☃☞☜☝☟㊙⓪ⅪⅫ▁▂▃▄▅▆▇█▏▎▍▌▋▊▉▔▕╭╮╰╯═╞╪╡◢◣◥◤╱╲╳︐凜熙❶❷❸❹❺❻❼❽❾ⅺⅻⅿℊ℉℻〄⇆⇄⇅ヷヸヹヺ㊝㍻㌳㍎纊褜鍈銈蓜俉炻昱棈鋹曻彅⼁丨仡仼伀伃伹佖侒侊侚侔俍偀倢俿倞偆偰偂傔僴僘兊兤冝冾凬刕劜劦勀勛匀匇匤卲厓厲叝﨎咜咊咩哿喆坙坥垬埈埇﨏塚增墲夋奓奛奝奣妤妺孖寀甯寘寬尞岦岺峵崧嵓﨑嵂嵭嶸嶹巐弡弴彧德忞恝悅悊惞惕愠惲愑愷愰憘戓抦揵摠撝擎敎昀昕昉昮昞昤晥晗晙晴晳暙暠暲暿曺朎朗杦枻桒柀栁桄棏﨓楨﨔榘槢樰橫橆橳橾櫢櫤毖氿汜沆汯泚洄涇浯涖涬淏淸淲淼渹湜渧渼溿澈澵濵瀅瀇瀨炅炫焏焄煜煆煇凞燁燾犱犾猤猪獷玽珉珖珣珒琇珵琦琪琩琮瑢璉璟甁畯皂皜皞皛皦益睆劯砡硎硤硺礰礼神祥禔福禛竑竧靖竫箞精絈絜綷綠緖繒罇羡羽茁荢荿菇菶葈蒴蕓蕙蕫﨟薰蘒﨡蠇裵訒訷詹誧誾諟諸諶譓譿賰賴贒赶﨣軏﨤逸遧郞都鄕鄧釚釗釞釭釮釤釥鈆鈐鈊鈺鉀鈼鉎鉙鉑鈹鉧銧鉷鉸鋧鋗鋙鋐﨧鋕鋠鋓錥錡鋻﨨錞鋿錝錂鍰鍗鎤鏆鏞鏸鐱鑅鑈閒隆﨩隝隯霳霻靃靍靏⾭靑靕顗顥飯飼餧館馞驎髙髜魵魲鮏鮱鮻鰀鵰鵫鶴鸙黑丬丰么兹勐厉呕圣夹娄尔峦帘帮庙强扑挛权欤毡沪洁涂渗滦灾玺珏璇瓯疴癯瞩离笔腊腌艹虬虽裆蹰迈适霉鼗鼹拼么啊傻热`.split(''); 2 | 3 | // 220806 4 | // 撿摻攙攄攖擷擻棖檉樁欞櫬檟櫧鍊龔齗鷖鱣鸕鵂鸝鷫鯁鯗鯇鯽鯫鯧鯷鱷馱馹騶驊騂騭騤顙騖騮騸頹顒顓儞 5 | // 顬頎頊韞韙靚陘閫閱閽閩鑲鐿鏢飥餳饜颸颺鎛鍇鍥錟鋌銳鋟錕鏽鈳鈸鄺酈醞釷 6 | // 蛺纘繳縑緣緱繢氳汙漚渢灝煒硨獼穠穭穌籙籮礱糝翬緹縕聵膁睜癭癱瘞瓚璫瑋 7 | // 產倀傖儈儐擊辦曆吳噦噲囉噯嚕壩壚坰婭娛媧嬙孿 8 | // 褘讞讜詿縈蠆蠔璵璣絝綆綌蓀臏豔紇紈紉紝紓蓽滎熒藎丟歲峴嶠嶔幬幘龐錄懟愜戩戶掄撾 9 | // 賙賡賾贛赬躒蹺軑軔軹訕詎訁 -------------------------------------------------------------------------------- /html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 福音战士標題生成器 - Evangelion Title Card Generator - 神奇海螺实验室 8 | 9 | 10 | 11 | 12 | 13 |
14 |

福音戰士標題生成器

15 |

Evangelion Title Card Generator

16 |
17 |
18 | 2022-07-12:支持 iOS、Mac Safari、Firefox 啦 19 |
20 |
21 |
22 |

排版

23 | 29 | 30 | 31 |
32 |
33 |
34 |

輸出

35 | 36 | 37 |
38 |
39 | 40 |

文字

41 |
42 |
43 | 47 | 52 |
53 |
54 |

未匹配文字

55 |
{{noMatchMojis.join(' , ')}}
56 | 57 | 58 |

59 | 遇到不匹配字型可先嘗試轉繁體,還不匹配的情况請嘗試替換其他文字
60 | 2022-11-26 不匹配字型调整为空白显示,请确保字型存在 61 | #31
62 | 实在找不到替换文字可以偷懒安装一份 思源宋体 Heavy 字型作为 Fallbacks 63 |

64 |
65 |

樣例

66 |
67 |
71 | {{input.placeholder}} 72 |
73 |
77 | {{text}} 78 |
79 |
80 |
81 | 82 |
83 | 84 | 91 |
92 | 93 |
94 | 97 |
98 | 99 |
100 |
101 | 模糊 102 | 顆粒 103 | 銳化 104 | Type 95 105 |
106 |
107 | 108 |
109 |
110 |

111 | 本机安装 Matisse-EB 字体的情况会得到最佳使用体验
112 | 建议优先安装 EVA-Matisse-EB,会更有福音战士味儿
113 | Fontworks 的 EVA 联动订阅字体授权在 mojimo.jp/eva 114 |

115 |

116 | 本机已安装 Matisse-EB 字体,字体授权情况请自行确认 117 |

118 | 120 |
121 |
122 |
123 |
124 |

生成好啦

125 | 126 | 129 |

手机端保存失败时可尝试长按图片 “添加到照片”

130 |
131 | 132 |
133 |

134 | 生成图片仅供预览,再利用需自行购买字体授权
135 | 请使用自带浏览器进行保存图片 136 | 137 | 图片不够旧?可以试试再来几年 电子包浆
138 | 图片不够蒸?可以试试虚拟虚拟信号 蒸 気 機 139 |
140 |

141 |
142 |
143 |
144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 190 | 202 | -------------------------------------------------------------------------------- /html/layouts.js: -------------------------------------------------------------------------------- 1 | const layouts = [ 2 | { 3 | id: 'e1', 4 | title: '第壱話 使徒、襲来', 5 | inputs:[ 6 | { 7 | placeholder:'使徒', 8 | minLength:2, 9 | maxLength:8, 10 | }, 11 | { 12 | placeholder:'襲来', 13 | minLength:2, 14 | maxLength:8, 15 | }, 16 | { 17 | placeholder:'第壱話', 18 | minLength:0, 19 | maxLength:6, 20 | } 21 | ], 22 | config:{ 23 | noise: true, 24 | }, 25 | exemples: [ 26 | // [ 27 | // '媽的', 28 | // '好熱', 29 | // '不想上班' 30 | // ] 31 | ], 32 | make:({ 33 | canvas, 34 | ctx, 35 | texts, 36 | config, 37 | functions, 38 | })=>{ 39 | const { 40 | width, 41 | height, 42 | scale, 43 | padding, 44 | space, 45 | } = config; 46 | 47 | const { 48 | randOne, 49 | setCtxConfig, 50 | makeTextCanvas, 51 | makeTextSizeDiffCanvas, 52 | makeLinesCanvas, 53 | makeLinesDiffSizeCanvas, 54 | makeVerticalTextCanvas, 55 | } = functions; 56 | 57 | const [a,b,sub] = texts; 58 | const aLength = a.length; 59 | const bLength = b.length; 60 | 61 | const titleCanvas = (_=>{ 62 | const canvas = document.createElement('canvas'); 63 | const ctx = canvas.getContext('2d'); 64 | const verticalCanvas = makeVerticalTextCanvas(a,-space) 65 | const pointCanvas = makeTextCanvas('、') 66 | const horizontalCanvas = makeTextCanvas(b,-space,padding) 67 | 68 | const width = Math.ceil( horizontalCanvas.width / bLength * (bLength+0.3) ) + verticalCanvas.width + padding 69 | const height = verticalCanvas.height 70 | 71 | canvas.width = width 72 | canvas.height = height 73 | 74 | ctx.drawImage( 75 | verticalCanvas, 0,0 76 | ) 77 | ctx.drawImage( 78 | pointCanvas, 79 | verticalCanvas.width - space * 2, height - horizontalCanvas.height 80 | ) 81 | ctx.drawImage( 82 | horizontalCanvas, 83 | verticalCanvas.width * 1.3, height - horizontalCanvas.height + padding / 3 84 | ) 85 | return canvas 86 | })() 87 | 88 | if(sub){ 89 | const subCanvas = makeTextCanvas(sub,-space) 90 | let subHeight = height * 0.19; 91 | let subWidth = subHeight / subCanvas.height * subCanvas.width; 92 | 93 | ctx.drawImage( 94 | subCanvas, 95 | padding,padding, 96 | subWidth,subHeight 97 | ); 98 | let titleHeight = height - padding * 2 - subHeight 99 | let titleWidth = Math.min( titleHeight / titleCanvas.height * titleCanvas.width, width - padding ) 100 | ctx.drawImage( 101 | titleCanvas, 102 | padding , padding + subHeight, 103 | titleWidth , titleHeight 104 | ) 105 | }else{ 106 | 107 | let titleHeight = height - padding * 2 108 | let titleWidth = Math.min(titleHeight / titleCanvas.height * titleCanvas.width , width - padding ) 109 | ctx.drawImage( 110 | titleCanvas, 111 | padding , (height - titleHeight) / 2, 112 | titleWidth , titleHeight 113 | ) 114 | } 115 | } 116 | }, 117 | { 118 | id: 'e13', 119 | title: '第拾参話 使徒、侵入', 120 | inputs:[ 121 | { 122 | placeholder:'使徒', 123 | minLength:2, 124 | maxLength:8, 125 | }, 126 | { 127 | placeholder:'侵入', 128 | minLength:2, 129 | maxLength:8, 130 | }, 131 | { 132 | placeholder:'第拾参話', 133 | minLength:0, 134 | maxLength:6, 135 | } 136 | ], 137 | exemples:[ 138 | 139 | [ 140 | 'ネルフ', 141 | '誕生', 142 | '第弐拾壱話' 143 | ], 144 | [ 145 | 'せめて、人間', 146 | 'らしく', 147 | '第弐拾弐話' 148 | ] 149 | ], 150 | make:({ 151 | canvas, 152 | ctx, 153 | texts, 154 | config, 155 | functions, 156 | })=>{ 157 | const { 158 | width, 159 | height, 160 | scale, 161 | padding, 162 | fontSize, 163 | space, 164 | } = config; 165 | 166 | const { 167 | randOne, 168 | setCtxConfig, 169 | makeTextCanvas, 170 | makeTextSizeDiffCanvas, 171 | makeLinesCanvas, 172 | makeLinesDiffSizeCanvas, 173 | makeVerticalTextCanvas, 174 | } = functions; 175 | 176 | const [a,b,sub] = texts; 177 | const aLength = a.length; 178 | const bLength = b.length; 179 | 180 | const titleCanvas = (_=>{ 181 | const canvas = document.createElement('canvas'); 182 | const ctx = canvas.getContext('2d'); 183 | const aCanvas = makeTextSizeDiffCanvas(a,-space) 184 | const pointCanvas = makeTextCanvas('、') 185 | const bCanvas = makeVerticalTextCanvas(b) 186 | 187 | const width = Math.ceil( aCanvas.width / bLength * (bLength+0.2) ) + bCanvas.width 188 | const height = bCanvas.height 189 | 190 | canvas.width = width 191 | canvas.height = height 192 | 193 | ctx.drawImage(aCanvas, 0,0) 194 | ctx.drawImage( 195 | pointCanvas, 196 | aCanvas.width - fontSize * 0.05 , 0 , 197 | pointCanvas.width * 0.8,pointCanvas.height * 1.14 198 | ) 199 | ctx.drawImage(bCanvas, width - bCanvas.width, 0) 200 | return canvas 201 | })(); 202 | 203 | let titleWidth = width - padding * 2 204 | let titleHeight = titleWidth / titleCanvas.width * titleCanvas.height; 205 | let maxTitleHeight = height - padding * 2 206 | titleHeight = Math.min(titleHeight,maxTitleHeight) 207 | // titleWidth = Math.min(titleWidth,width - padding * 2) 208 | ctx.drawImage( 209 | titleCanvas, 210 | padding , padding, 211 | titleWidth , titleHeight 212 | ); 213 | 214 | if(sub){ 215 | const subCanvas = makeTextCanvas(sub,-space) 216 | let subHeight = height * 0.19; 217 | let subWidth = subHeight / subCanvas.height * subCanvas.width; 218 | 219 | ctx.drawImage( 220 | subCanvas, 221 | padding,height - padding - subHeight, 222 | subWidth,subHeight 223 | ); 224 | } 225 | 226 | } 227 | }, 228 | { 229 | id: 'e25', 230 | title: '第弐拾伍話 終わる世界', 231 | inputs:[ 232 | { 233 | placeholder:'終わる世界', 234 | minLength:2, 235 | maxLength:14, 236 | }, 237 | { 238 | placeholder:'第弐拾伍話', 239 | minLength:2, 240 | maxLength:14, 241 | } 242 | ], 243 | make:({ 244 | canvas, 245 | ctx, 246 | texts, 247 | config, 248 | functions, 249 | })=>{ 250 | const { 251 | width, 252 | height, 253 | scale, 254 | padding, 255 | space, 256 | } = config; 257 | 258 | const { 259 | randOne, 260 | setCtxConfig, 261 | makeTextCanvas, 262 | makeTextSizeDiffCanvas, 263 | makeLinesCanvas, 264 | makeLinesDiffSizeCanvas, 265 | makeVerticalTextCanvas, 266 | } = functions; 267 | 268 | const [text,sub] = texts; 269 | 270 | 271 | // 标题在中 272 | const textCanvas = makeTextSizeDiffCanvas(text,-space * 1.4) 273 | ctx.drawImage( 274 | textCanvas, 275 | width * 0.2,height * 0.44, 276 | width * 0.6,height/4 277 | ) 278 | 279 | if(sub){ 280 | let subCanvas = makeTextCanvas(sub,-space) 281 | let subHeight = height * 0.12; 282 | let subWidth = subHeight / subCanvas.height * subCanvas.width * 0.8; 283 | 284 | ctx.drawImage( 285 | subCanvas, 286 | (width - subWidth) / 2, height * 0.26, 287 | subWidth,subHeight 288 | ); 289 | } 290 | 291 | } 292 | }, 293 | { 294 | id: 'e12', 295 | title: '第拾弐話 奇跡の価値は', 296 | inputs:[ 297 | { 298 | placeholder:'奇跡の価値は', 299 | minLength:2, 300 | maxLength:14, 301 | }, 302 | { 303 | placeholder:'第拾弐話', 304 | minLength:2, 305 | maxLength:14, 306 | } 307 | ], 308 | make:({ 309 | canvas, 310 | ctx, 311 | texts, 312 | config, 313 | functions, 314 | })=>{ 315 | const { 316 | width, 317 | height, 318 | scale, 319 | padding, 320 | space, 321 | } = config; 322 | 323 | const { 324 | randOne, 325 | setCtxConfig, 326 | makeTextCanvas, 327 | makeTextSizeDiffCanvas, 328 | makeLinesCanvas, 329 | makeLinesDiffSizeCanvas, 330 | makeVerticalTextCanvas, 331 | } = functions; 332 | 333 | const [text,sub] = texts; 334 | 335 | // 标题在上 336 | const textCanvas = makeTextSizeDiffCanvas(text,-space * 1.4) 337 | const textWidth = width - padding * 2 338 | const textHeight = Math.min(textWidth / textCanvas.width * textCanvas.height * 1.4,height / 2) 339 | ctx.drawImage( 340 | textCanvas, 341 | padding, padding * 2, 342 | textWidth, textHeight 343 | ) 344 | 345 | if(sub){ 346 | let subCanvas = makeTextCanvas(sub,-space) 347 | let subHeight = height * 0.2; 348 | let subWidth = subHeight / subCanvas.height * subCanvas.width * 0.8; 349 | let subLeft = randOne([ 350 | padding, // left 351 | // (width - subWidth) / 2, //center 352 | // width - subWidth - padding // right 353 | ]); 354 | 355 | ctx.drawImage( 356 | subCanvas, 357 | subLeft, height - subHeight - padding * 2, 358 | subWidth,subHeight 359 | ); 360 | } 361 | } 362 | }, 363 | { 364 | id: 'e3', 365 | title: '第参話 鳴らない、電話', 366 | inputs:[ 367 | { 368 | placeholder:'鳴らない、電話', 369 | minLength:2, 370 | maxLength:14, 371 | }, 372 | { 373 | placeholder:'第参話', 374 | minLength:2, 375 | maxLength:14, 376 | } 377 | ], 378 | make:({ 379 | canvas, 380 | ctx, 381 | texts, 382 | config, 383 | functions, 384 | })=>{ 385 | const { 386 | width, 387 | height, 388 | scale, 389 | padding, 390 | space, 391 | } = config; 392 | 393 | const { 394 | randOne, 395 | setCtxConfig, 396 | makeTextCanvas, 397 | makeTextSizeDiffCanvas, 398 | makeLinesCanvas, 399 | makeLinesDiffSizeCanvas, 400 | makeVerticalTextCanvas, 401 | } = functions; 402 | 403 | const [text,sub] = texts; 404 | 405 | let sliceIndex = Math.round(text.length * 0.8) 406 | const a = text.slice(0,sliceIndex) 407 | const b = text.slice(sliceIndex) 408 | 409 | // 标题在上 410 | const aCanvas = makeTextSizeDiffCanvas(a,-space * 1.4) 411 | const aWidth = width - padding * 5 412 | const aHeight = Math.min(aWidth / aCanvas.width * aCanvas.height * 1.15,height / 2) 413 | ctx.drawImage( 414 | aCanvas, 415 | padding * 2, padding * 1.5, 416 | aWidth, aHeight 417 | ) 418 | 419 | const bCanvas = makeTextSizeDiffCanvas(b,-space * 1.4) 420 | const bHeight = aHeight * 0.95 421 | const bWidth = Math.min(bHeight * bCanvas.width / bCanvas.height,height / 2) * 0.9 422 | 423 | const bottomPadding = padding * 2.5 424 | ctx.drawImage( 425 | bCanvas, 426 | padding * 2, height - bHeight - bottomPadding, 427 | bWidth, bHeight 428 | ) 429 | 430 | if(sub){ 431 | let subCanvas = makeVerticalTextCanvas(sub,-space) 432 | let subHeight = height * 0.32; 433 | let subWidth = subHeight / subCanvas.height * subCanvas.width * 1.1; 434 | 435 | ctx.drawImage( 436 | subCanvas, 437 | width - subWidth - padding * 4, height - subHeight - bottomPadding, 438 | subWidth,subHeight 439 | ); 440 | } 441 | } 442 | }, 443 | { 444 | id: 'e25-2', 445 | title: '第弐拾伍話 終わる世界', 446 | inputs:[ 447 | { 448 | placeholder:'終わる世界', 449 | minLength:2, 450 | maxLength:14, 451 | }, 452 | { 453 | placeholder:'第弐拾伍話', 454 | minLength:2, 455 | maxLength:14, 456 | }, 457 | { 458 | type:'tab', 459 | name:'大标题位置', 460 | options:[ 461 | { 462 | "value": 0, 463 | "text": "偏下" 464 | }, 465 | { 466 | "value": 1, 467 | "text": "偏左" 468 | }, 469 | { 470 | "value": 2, 471 | "text": "偏右" 472 | } 473 | ] 474 | }, 475 | { 476 | type:'tab', 477 | name:'小标题位置', 478 | options:[ 479 | { 480 | "value": 0, 481 | "text": "起始" 482 | }, 483 | { 484 | "value": 1, 485 | "text": "居中" 486 | }, 487 | { 488 | "value": 2, 489 | "text": "结束" 490 | } 491 | ] 492 | }, 493 | ], 494 | make:({ 495 | canvas, 496 | ctx, 497 | texts, 498 | config, 499 | functions, 500 | })=>{ 501 | const { 502 | width, 503 | height, 504 | scale, 505 | padding, 506 | space, 507 | } = config; 508 | 509 | const { 510 | randOne, 511 | setCtxConfig, 512 | makeTextCanvas, 513 | makeTextSizeDiffCanvas, 514 | makeLinesCanvas, 515 | makeLinesDiffSizeCanvas, 516 | makeVerticalTextCanvas, 517 | } = functions; 518 | 519 | const [text,sub,a,b] = texts; 520 | 521 | // console.log(text,sub,a,b) 522 | 523 | randOne([ 524 | _=>{ 525 | // 标题在下 526 | const textCanvas = makeTextSizeDiffCanvas(text,-space * 1.4) 527 | const textWidth = width - padding * 2 528 | const textHeight = Math.min(textWidth / textCanvas.width * textCanvas.height,height - padding * 2) 529 | ctx.drawImage( 530 | textCanvas, 531 | padding, height - padding * 2 - textHeight, 532 | textWidth, textHeight 533 | ) 534 | 535 | if(sub){ 536 | let subCanvas = makeTextCanvas(sub,-space) 537 | let subHeight = height * 0.2; 538 | let subWidth = subHeight / subCanvas.height * subCanvas.width * 0.8; 539 | let subLeft = randOne([ 540 | padding, // left 541 | (width - subWidth) / 2, //center 542 | width - subWidth - padding // right 543 | ],b); 544 | 545 | ctx.drawImage( 546 | subCanvas, 547 | subLeft, padding * 2, 548 | subWidth,subHeight 549 | ); 550 | } 551 | }, 552 | _=>{ 553 | // 标题在左 sub在右 554 | const textCanvas = makeVerticalTextCanvas(text,-space * 1.4) 555 | const textHeight = height - padding * 2 556 | const textWidth = Math.min(textHeight / textCanvas.height * textCanvas.width, width - padding * 2) * rand(0.8,1.4) 557 | 558 | ctx.drawImage( 559 | textCanvas, 560 | padding, padding, 561 | textWidth, textHeight 562 | ) 563 | 564 | if(sub){ 565 | let subCanvas = makeTextCanvas(sub,-space) 566 | let subHeight = height * 0.18; 567 | let subWidth = Math.min( 568 | subHeight / subCanvas.height * subCanvas.width * 0.8, 569 | width - textWidth - padding * 2 570 | ); 571 | const subLeft = width - padding - subWidth; 572 | 573 | const subTop = randOne([ 574 | padding, 575 | (height - subHeight) / 2, // middle 576 | height - subHeight - padding 577 | ],b); 578 | ctx.drawImage( 579 | subCanvas, 580 | subLeft, subTop, 581 | subWidth,subHeight 582 | ); 583 | } 584 | }, 585 | _=>{ 586 | // 标题在右 sub在左 587 | const textCanvas = makeVerticalTextCanvas(text,-space * 1.4) 588 | const textHeight = height - padding * 2 589 | const textWidth = Math.min(textHeight / textCanvas.height * textCanvas.width, width - padding * 2) * rand(0.8,1.4) 590 | 591 | ctx.drawImage( 592 | textCanvas, 593 | width - padding - textWidth, padding, 594 | textWidth, textHeight 595 | ) 596 | 597 | if(sub){ 598 | let subCanvas = makeTextCanvas(sub,-space) 599 | let subHeight = height * 0.18; 600 | let subWidth = Math.min( 601 | subHeight / subCanvas.height * subCanvas.width * 0.8, 602 | width - textWidth - padding * 2 603 | ); 604 | const subLeft = padding; 605 | 606 | const subTop = randOne([ 607 | padding, 608 | (height - subHeight) / 2, // middle 609 | height - subHeight - padding 610 | ],b); 611 | ctx.drawImage( 612 | subCanvas, 613 | subLeft, subTop, 614 | subWidth,subHeight 615 | ); 616 | } 617 | }, 618 | ],a)(); 619 | } 620 | }, 621 | { 622 | id: 'e4', 623 | title: '第四話 雨、逃げ出した後', 624 | inputs:[ 625 | { 626 | placeholder:'雨', 627 | length:1, 628 | }, 629 | { 630 | placeholder:'逃げ出した後', 631 | minLength:2, 632 | maxLength:14, 633 | }, 634 | { 635 | placeholder:'第四話', 636 | minLength:2, 637 | maxLength:10 638 | } 639 | ], 640 | exemples:[ 641 | [ 642 | '決戦', 643 | '第3新東京市', 644 | '第六話' 645 | ], 646 | [ 647 | '請', 648 | '我吃麦当勞', 649 | '求求了' 650 | ], 651 | // [ 652 | // '使徒', 653 | // '侵入', 654 | // '第拾参話', 655 | // ], 656 | ], 657 | make:({ 658 | canvas, 659 | ctx, 660 | texts, 661 | config, 662 | functions, 663 | })=>{ 664 | const { 665 | width, 666 | height, 667 | scale, 668 | padding, 669 | space, 670 | } = config; 671 | 672 | const { 673 | randOne, 674 | setCtxConfig, 675 | makeTextCanvas, 676 | makeTextSizeDiffCanvas, 677 | makeLinesCanvas, 678 | makeLinesDiffSizeCanvas, 679 | makeVerticalTextCanvas, 680 | } = functions; 681 | 682 | const [a,b,sub] = texts; 683 | 684 | const aCanvas = makeTextCanvas(a,-space) 685 | 686 | const aHeight = height * 0.43 687 | let maxaWidth = width * 0.5; 688 | 689 | if(b.length <= 2){ 690 | maxaWidth = width * 0.54 691 | } 692 | 693 | const aWidth = Math.min( aHeight / aCanvas.height * aCanvas.width , maxaWidth ); 694 | const aPaddingScale = 1 - (b.length - a.length) / 10 695 | // console.log({aPaddingScale}) 696 | ctx.drawImage(aCanvas, 697 | padding , padding + padding * aPaddingScale, 698 | aWidth, aHeight, 699 | ) 700 | 701 | const pointCanvas = makeTextCanvas('、') 702 | ctx.drawImage( 703 | pointCanvas, 704 | padding + aWidth - width * 0.02, padding * 2 , 705 | width * 0.24, width * 0.35, 706 | ) 707 | 708 | 709 | const bCanvas = (_=>{ 710 | // b字符需要拐个弯 711 | const bSliceIndex = Math.floor(b.length/2) 712 | const ba = b.slice(0,bSliceIndex) 713 | const bb = b.slice(bSliceIndex) 714 | // console.log({ba,bb}) 715 | 716 | const baCanvas = makeTextCanvas(ba,-space) 717 | const bbCanvas = makeVerticalTextCanvas(bb,-space) 718 | 719 | 720 | 721 | const canvas = document.createElement('canvas'); 722 | const ctx = canvas.getContext('2d'); 723 | 724 | canvas.width = baCanvas.width; 725 | canvas.height = baCanvas.height + bbCanvas.height; 726 | 727 | ctx.drawImage(baCanvas, 0,0) 728 | ctx.drawImage(bbCanvas, 729 | baCanvas.width - bbCanvas.width,baCanvas.height - space, 730 | bbCanvas.width,bbCanvas.height 731 | ) 732 | return canvas; 733 | })()//:makeVerticalTextCanvas(b,-space); 734 | 735 | let bWidth = width * 0.5 - padding 736 | let bHeight = bWidth / bCanvas.width * bCanvas.height 737 | 738 | const maxbHeight = (height - padding * 4); 739 | if(bHeight > maxbHeight){ 740 | bHeight = maxbHeight; 741 | bWidth = bHeight / bCanvas.height * bCanvas.width; 742 | } 743 | ctx.drawImage(bCanvas, 744 | width - bWidth - padding * 2, padding * 2, 745 | bWidth, bHeight 746 | ) 747 | 748 | 749 | const subCanvas = makeTextCanvas(sub,-space) 750 | let subHeight = height * 0.19; 751 | let subWidth = subHeight / subCanvas.height * subCanvas.width; 752 | 753 | ctx.drawImage( 754 | subCanvas, 755 | padding * 1.5,height - padding - subHeight - space * 2, 756 | subWidth,subHeight 757 | ); 758 | } 759 | }, 760 | { 761 | id: 'air', 762 | title: 'air', 763 | inputs:[ 764 | { 765 | placeholder:'air', 766 | minLength:2, 767 | maxLength:20, 768 | }, 769 | ], 770 | make:({ 771 | canvas, 772 | ctx, 773 | texts, 774 | config, 775 | functions, 776 | })=>{ 777 | const { 778 | width, 779 | height, 780 | scale, 781 | padding, 782 | space, 783 | fontColor, 784 | } = config; 785 | 786 | const { 787 | randOne, 788 | setCtxConfig, 789 | makeTextCanvas, 790 | makeTextSizeDiffCanvas, 791 | makeLinesCanvas, 792 | makeLinesDiffSizeCanvas, 793 | makeVerticalTextCanvas, 794 | } = functions; 795 | 796 | const [text,sub] = texts; 797 | 798 | // [air] 799 | // 小于十个字符 框框 800 | // const textCanvas = makeTextSizeDiffCanvas(text,-space); 801 | const textCanvas = makeTextCanvas(text); 802 | const _config = randOne([ 803 | { 804 | heightScale: 0.2, 805 | padding: padding, 806 | borderWidth: space * 0.5, 807 | }, 808 | // { 809 | // heightScale: 0.4, 810 | // padding: padding * 1.5, 811 | // borderWidth: space 812 | // }, 813 | // { 814 | // heightScale: 0.6, 815 | // padding: padding * 2, 816 | // borderWidth: space * 1.5 817 | // }, 818 | ]); 819 | const textHeight = height * _config.heightScale; 820 | const textWidth = Math.min(textHeight * textCanvas.width / textCanvas.height, width * 0.8); 821 | 822 | ctx.drawImage( 823 | textCanvas, 824 | (width - textWidth) / 2, ( height - textHeight ) / 2, 825 | textWidth,textHeight 826 | ); 827 | ctx.strokeStyle = fontColor; 828 | ctx.lineWidth = _config.borderWidth; 829 | const rectWidth = textWidth + _config.padding * 2; 830 | const rectHeight = textHeight + _config.padding; 831 | 832 | const rectLeft = (width - rectWidth) / 2; 833 | const rectTop = (height - rectHeight) / 2; 834 | ctx.strokeRect( 835 | rectLeft, rectTop, 836 | rectWidth, rectHeight 837 | ); 838 | 839 | } 840 | }, 841 | // { //书名号包裹的情况 842 | // title: '最後のシ者', 843 | // inputs:[ 844 | // { 845 | // placeholder:'最後のシ者', 846 | // minLength:2, 847 | // maxLength:10, 848 | // }, 849 | // ], 850 | // exemples:[ 851 | // // [ 852 | // // '【在世界中心呼喊】' 853 | // // ] 854 | // ], 855 | // make:({ 856 | // canvas, 857 | // ctx, 858 | // texts, 859 | // config, 860 | // functions, 861 | // })=>{ 862 | // const { 863 | // width, 864 | // height, 865 | // scale, 866 | // padding, 867 | // space, 868 | // fontColor, 869 | // } = config; 870 | 871 | // const { 872 | // randOne, 873 | // setCtxConfig, 874 | // makeTextCanvas, 875 | // makeTextSizeDiffCanvas, 876 | // makeLinesCanvas, 877 | // makeLinesDiffSizeCanvas, 878 | // makeVerticalTextCanvas, 879 | // } = functions; 880 | 881 | // const [text,sub] = texts; 882 | 883 | // let titleCanvas = makeTextSizeDiffCanvas(text,-space) 884 | // const _padding = padding * 2; 885 | // let titleWidth; 886 | // let titleHeight; 887 | // let titleTop = _padding; 888 | // let titleLeft = _padding; 889 | // let titleScale = titleCanvas.width / titleCanvas.height; 890 | 891 | // titleWidth = width - _padding * 2; 892 | // titleHeight = titleWidth / titleScale * 1.2 893 | // titleTop = (height - titleHeight) / 2 894 | 895 | 896 | // ctx.drawImage( 897 | // titleCanvas, 898 | // titleLeft , titleTop, 899 | // titleWidth, titleHeight 900 | // ) 901 | 902 | // } 903 | // }, 904 | { //书名号包裹的情况 905 | id: 'e24', 906 | title: '最後のシ者', 907 | inputs:[ 908 | { 909 | placeholder:'最後のシ者', 910 | minLength:2, 911 | maxLength:14, 912 | }, 913 | ], 914 | exemples:[ 915 | ], 916 | config:{ 917 | plan:'wb' 918 | }, 919 | make:({ 920 | canvas, 921 | ctx, 922 | texts, 923 | config, 924 | functions, 925 | })=>{ 926 | const { 927 | width, 928 | height, 929 | scale, 930 | padding, 931 | space, 932 | fontColor, 933 | } = config; 934 | 935 | const { 936 | randOne, 937 | setCtxConfig, 938 | makeTextCanvas, 939 | makeTextSizeDiffCanvas, 940 | makeLinesCanvas, 941 | makeLinesDiffSizeCanvas, 942 | makeVerticalTextCanvas, 943 | } = functions; 944 | 945 | const [text,sub] = texts; 946 | 947 | let titleCanvas = makeVerticalTextCanvas(text,-space) 948 | const _padding = padding * 2; 949 | let titleWidth; 950 | let titleHeight; 951 | let titleTop = _padding; 952 | let titleLeft = _padding; 953 | let titleScale = titleCanvas.width / titleCanvas.height; 954 | 955 | titleHeight = height - _padding * 2; 956 | titleWidth = titleHeight * titleScale * 1.2 957 | titleLeft = (width - titleWidth) / 2; 958 | 959 | ctx.drawImage( 960 | titleCanvas, 961 | titleLeft , titleTop, 962 | titleWidth, titleHeight 963 | ) 964 | 965 | } 966 | }, 967 | { //大于十个字符 六字多排 世界中心呼唤 968 | id:'e26', 969 | title: '最終話 世界の中心でアイを叫んだけもの', 970 | inputs:[ 971 | { 972 | placeholder:'世界の中心でアイを叫んだけもの', 973 | minLength:2, 974 | maxLength:40, 975 | }, 976 | { 977 | placeholder:'最終話', 978 | minLength:2, 979 | maxLength:14, 980 | }, 981 | ], 982 | make:({ 983 | canvas, 984 | ctx, 985 | texts, 986 | config, 987 | functions, 988 | })=>{ 989 | const { 990 | width, 991 | height, 992 | scale, 993 | padding, 994 | space, 995 | fontColor, 996 | } = config; 997 | 998 | const { 999 | randOne, 1000 | setCtxConfig, 1001 | makeTextCanvas, 1002 | makeTextSizeDiffCanvas, 1003 | makeLinesCanvas, 1004 | makeLinesDiffSizeCanvas, 1005 | makeVerticalTextCanvas, 1006 | } = functions; 1007 | 1008 | const [text,sub] = texts; 1009 | 1010 | const _texts = new Array(Math.ceil(text.length/6)).fill(1).map((_,index)=>{ 1011 | const start = index * 6; 1012 | const end = start + 6; 1013 | return text.slice(start,end) 1014 | }) 1015 | const titleCanvas = makeLinesCanvas(_texts,-space * 1.5); 1016 | ctx.drawImage( 1017 | titleCanvas, 1018 | width * 0.12,height * 0.25, 1019 | width * 0.76,height * 0.67 1020 | ) 1021 | 1022 | if(sub){ 1023 | const subCanvas = makeTextCanvas(sub,-space) 1024 | let subHeight = height * 0.15; 1025 | let subWidth = subHeight / subCanvas.height * subCanvas.width * 0.8; 1026 | 1027 | ctx.drawImage( 1028 | subCanvas, 1029 | (width - subWidth) / 2 - space, height * 0.1, 1030 | subWidth,subHeight 1031 | ); 1032 | } 1033 | 1034 | } 1035 | }, 1036 | { 1037 | id:'anno-kandoku', 1038 | title: '監督 庵野秀明', 1039 | inputs:[ 1040 | { 1041 | placeholder:'庵野秀明', 1042 | minLength:2, 1043 | maxLength:14, 1044 | }, 1045 | { 1046 | placeholder:'監督', 1047 | minLength:2, 1048 | maxLength:14, 1049 | }, 1050 | ], 1051 | exemples:[ 1052 | [ 1053 | 'アスカ、来日', 1054 | '第八話' 1055 | ], 1056 | // [ 1057 | // 'レイ、心のむこうに', 1058 | // '第伍話' 1059 | // ], 1060 | [ 1061 | 'ゼーレ、魂の座', 1062 | '第拾四話' 1063 | ], 1064 | ], 1065 | make:({ 1066 | canvas, 1067 | ctx, 1068 | texts, 1069 | config, 1070 | functions, 1071 | })=>{ 1072 | const { 1073 | width, 1074 | height, 1075 | scale, 1076 | padding, 1077 | space, 1078 | fontColor, 1079 | } = config; 1080 | 1081 | const { 1082 | randOne, 1083 | setCtxConfig, 1084 | makeTextCanvas, 1085 | makeTextSizeDiffCanvas, 1086 | makeLinesCanvas, 1087 | makeLinesDiffSizeCanvas, 1088 | makeVerticalTextCanvas, 1089 | } = functions; 1090 | 1091 | const [a,b] = texts; 1092 | 1093 | // ┗ 1094 | const titleCanvas = (_=>{ 1095 | 1096 | let aSliceIndex = Math.floor(a.length/2) 1097 | 1098 | const pointIndex = a.indexOf('、') 1099 | if(pointIndex !== -1) aSliceIndex = pointIndex 1100 | 1101 | const aa = a.slice(0,aSliceIndex) 1102 | const ab = a.slice(aSliceIndex) 1103 | 1104 | 1105 | const canvas = document.createElement('canvas'); 1106 | const ctx = canvas.getContext('2d'); 1107 | const verticalCanvas = makeVerticalTextCanvas(aa,-space) 1108 | const horizontalCanvas = makeTextSizeDiffCanvas(ab,-space) 1109 | 1110 | const width = horizontalCanvas.width + verticalCanvas.width - space 1111 | const height = verticalCanvas.height 1112 | 1113 | canvas.width = width 1114 | canvas.height = height 1115 | 1116 | ctx.drawImage(verticalCanvas, 0,0) 1117 | ctx.drawImage( 1118 | horizontalCanvas, 1119 | width - horizontalCanvas.width - space, 1120 | height - horizontalCanvas.height + padding / 3 1121 | ) 1122 | return canvas 1123 | })(); 1124 | 1125 | const subCanvas = makeTextCanvas(b,-space) 1126 | let subHeight = height * 0.2; 1127 | let subWidth = Math.min(subHeight / subCanvas.height * subCanvas.width,width - padding * 2); 1128 | 1129 | ctx.drawImage( 1130 | subCanvas, 1131 | width - subWidth - padding,padding, 1132 | subWidth,subHeight 1133 | ); 1134 | 1135 | let titleHeight = height - padding * 2 - subHeight 1136 | let titleWidth = Math.min(titleHeight / titleCanvas.height * titleCanvas.width, width - padding * 2) 1137 | ctx.drawImage( 1138 | titleCanvas, 1139 | padding , height - titleHeight - padding, 1140 | titleWidth , titleHeight 1141 | ) 1142 | 1143 | } 1144 | }, 1145 | { 1146 | id:'e15', 1147 | title: '第拾伍話 嘘と沈黙', 1148 | inputs:[ 1149 | { 1150 | placeholder:'嘘と沈黙', 1151 | minLength:2, 1152 | maxLength:14, 1153 | }, 1154 | { 1155 | placeholder:'第拾伍話', 1156 | minLength:2, 1157 | maxLength:14, 1158 | }, 1159 | ], 1160 | exemples:[ 1161 | [ 1162 | '石森章太郎', 1163 | '原作' 1164 | ], 1165 | // [ 1166 | // '嘘と沈黙', 1167 | // '第拾伍話' 1168 | // ] 1169 | ], 1170 | config:{ 1171 | plan:'wb' 1172 | }, 1173 | make:({ 1174 | canvas, 1175 | ctx, 1176 | texts, 1177 | config, 1178 | functions, 1179 | })=>{ 1180 | const { 1181 | width, 1182 | height, 1183 | scale, 1184 | padding, 1185 | space, 1186 | fontColor, 1187 | } = config; 1188 | 1189 | const { 1190 | randOne, 1191 | setCtxConfig, 1192 | makeTextCanvas, 1193 | makeTextSizeDiffCanvas, 1194 | makeLinesCanvas, 1195 | makeLinesDiffSizeCanvas, 1196 | makeVerticalTextCanvas, 1197 | } = functions; 1198 | 1199 | const [a,b] = texts; 1200 | // ┒ 1201 | const titleCanvas = (_=>{ 1202 | 1203 | const aSliceIndex = Math.ceil(a.length/2) 1204 | const aa = a.slice(0,aSliceIndex) 1205 | const ab = a.slice(aSliceIndex) 1206 | 1207 | 1208 | const canvas = document.createElement('canvas'); 1209 | const ctx = canvas.getContext('2d'); 1210 | const aaCanvas = makeTextCanvas(aa,-space) 1211 | const abCanvas = makeVerticalTextCanvas(ab,-space) 1212 | 1213 | const width = aaCanvas.width + abCanvas.width - space 1214 | const height = abCanvas.height 1215 | 1216 | canvas.width = width 1217 | canvas.height = height 1218 | 1219 | ctx.drawImage(aaCanvas, 0,0) 1220 | ctx.drawImage(abCanvas, aaCanvas.width - space, 0) 1221 | return canvas 1222 | })(); 1223 | 1224 | const subCanvas = makeVerticalTextCanvas(b,-space) 1225 | let subWidth = width * 0.15; 1226 | let subHeight = Math.min(subWidth * subCanvas.height / subCanvas.width * 1.1,height/2); 1227 | subWidth = Math.min(subWidth,subHeight /subCanvas.height * subCanvas.width *1.2 ); 1228 | ctx.drawImage( 1229 | subCanvas, 1230 | padding,padding * 2, 1231 | subWidth,subHeight 1232 | ); 1233 | 1234 | let titleWidth = width - padding * 6 - subWidth 1235 | let titleHeight = Math.max( titleWidth / titleCanvas.width * titleCanvas.height , height * 0.6) 1236 | ctx.drawImage( 1237 | titleCanvas, 1238 | width - titleWidth - padding * 3 , height - titleHeight - padding * 2, 1239 | titleWidth , titleHeight 1240 | ) 1241 | } 1242 | }, 1243 | { 1244 | id:'eng-title', 1245 | title: 'NEON GENESIS EVANGELION Rei II', 1246 | inputs:[ 1247 | { 1248 | placeholder:'NEON GENESIS EVANGELION', 1249 | minLength:2, 1250 | maxLength:40, 1251 | }, 1252 | { 1253 | placeholder:'EPISODE:13', 1254 | minLength:2, 1255 | maxLength:20, 1256 | }, 1257 | { 1258 | placeholder:'Rei II', 1259 | minLength:2, 1260 | maxLength:14, 1261 | }, 1262 | ], 1263 | make:({ 1264 | canvas, 1265 | ctx, 1266 | texts, 1267 | config, 1268 | functions, 1269 | })=>{ 1270 | const { 1271 | width, 1272 | height, 1273 | scale, 1274 | padding, 1275 | space, 1276 | fontColor, 1277 | } = config; 1278 | 1279 | const { 1280 | randOne, 1281 | setCtxConfig, 1282 | makeTextCanvas, 1283 | makeTextSizeDiffCanvas, 1284 | makeLinesCanvas, 1285 | makeLinesDiffSizeCanvas, 1286 | makeVerticalTextCanvas, 1287 | } = functions; 1288 | 1289 | const [a,b,c] = texts; 1290 | 1291 | 1292 | 1293 | 1294 | const engs = a.split(/\s+/g); 1295 | const titleCanvas = makeLinesDiffSizeCanvas(engs,{lineHeight:0.9}) 1296 | 1297 | let titleWidth = width - padding * 2 1298 | let titleHeight = titleWidth / titleCanvas.width * titleCanvas.height * 1.3; 1299 | let maxTitleHeight = height * 0.6 1300 | titleHeight = Math.min(titleHeight,maxTitleHeight) 1301 | // titleWidth = Math.min(titleWidth,width - padding * 2) 1302 | ctx.drawImage( 1303 | titleCanvas, 1304 | padding , padding, 1305 | titleWidth , titleHeight 1306 | ); 1307 | 1308 | 1309 | const bCanvas = makeTextCanvas(b,-space) 1310 | let bHeight = height * 0.16; 1311 | let bWidth = bHeight / bCanvas.height * bCanvas.width *0.7; 1312 | 1313 | ctx.drawImage( 1314 | bCanvas, 1315 | padding,titleHeight + padding * 1.4, 1316 | bWidth, bHeight 1317 | ); 1318 | 1319 | const cCanvas = makeTextCanvas(c,-space) 1320 | let cHeight = height * 0.2; 1321 | let cWidth = cHeight / cCanvas.height * cCanvas.width; 1322 | 1323 | ctx.drawImage( 1324 | cCanvas, 1325 | width - cWidth - padding,height - padding - cHeight, 1326 | cWidth, cHeight 1327 | ); 1328 | 1329 | 1330 | } 1331 | }, 1332 | { 1333 | id:'do-you-love-me', 1334 | title: 'Do you love me?', 1335 | inputs:[ 1336 | { 1337 | placeholder:'Do you love me?', 1338 | minLength:2, 1339 | maxLength:20, 1340 | }, 1341 | { 1342 | placeholder:'EPISODE:25', 1343 | minLength:2, 1344 | maxLength:20, 1345 | }, 1346 | ], 1347 | make:({ 1348 | canvas, 1349 | ctx, 1350 | texts, 1351 | config, 1352 | functions, 1353 | })=>{ 1354 | const { 1355 | width, 1356 | height, 1357 | scale, 1358 | padding, 1359 | space, 1360 | fontColor, 1361 | } = config; 1362 | 1363 | const { 1364 | randOne, 1365 | setCtxConfig, 1366 | makeTextCanvas, 1367 | makeTextSizeDiffCanvas, 1368 | makeLinesCanvas, 1369 | makeLinesDiffSizeCanvas, 1370 | makeVerticalTextCanvas, 1371 | } = functions; 1372 | 1373 | const [text,sub] = texts; 1374 | 1375 | 1376 | const textCanvas = makeTextSizeDiffCanvas(text,-4) 1377 | const textWidth = width * 0.8; 1378 | const textHeight = Math.min(textWidth / textCanvas.width * textCanvas.height * 1.8, height * 0.8); 1379 | 1380 | 1381 | if(sub){ 1382 | let subCanvas = makeTextCanvas(sub,-space) 1383 | let subHeight = height * 0.11; 1384 | let subWidth = subHeight / subCanvas.height * subCanvas.width * 0.6; 1385 | 1386 | ctx.drawImage( 1387 | subCanvas, 1388 | (width - subWidth) / 2, height * 0.3, 1389 | subWidth,subHeight 1390 | ); 1391 | ctx.drawImage( 1392 | textCanvas, 1393 | width * 0.1,height * 0.42, 1394 | textWidth, textHeight 1395 | ) 1396 | }else{ 1397 | ctx.drawImage( 1398 | textCanvas, 1399 | width * 0.1, height * 0.1, 1400 | textWidth, textHeight 1401 | ) 1402 | } 1403 | 1404 | } 1405 | }, 1406 | { //大于十个字符 六字多排 世界中心呼唤 1407 | id:'e20', 1408 | title: '第弐拾話 心のかたち 人のかたち', 1409 | inputs:[ 1410 | { 1411 | placeholder:'心のかたち', 1412 | minLength:2, 1413 | maxLength:14, 1414 | }, 1415 | { 1416 | placeholder:'人のかたち', 1417 | minLength:2, 1418 | maxLength:14, 1419 | }, 1420 | { 1421 | placeholder:'第弐拾話', 1422 | minLength:2, 1423 | maxLength:14, 1424 | }, 1425 | ], 1426 | exemples:[ 1427 | [ 1428 | '今日臭臭臭', 1429 | '明日香香香', 1430 | '最喜歡的一話', 1431 | ], 1432 | ], 1433 | make:({ 1434 | canvas, 1435 | ctx, 1436 | texts, 1437 | config, 1438 | functions, 1439 | })=>{ 1440 | const { 1441 | width, 1442 | height, 1443 | scale, 1444 | padding, 1445 | space, 1446 | fontColor, 1447 | } = config; 1448 | 1449 | const { 1450 | randOne, 1451 | setCtxConfig, 1452 | makeTextCanvas, 1453 | makeTextSizeDiffCanvas, 1454 | makeLinesCanvas, 1455 | makeLinesDiffSizeCanvas, 1456 | makeVerticalTextCanvas, 1457 | } = functions; 1458 | 1459 | let [a,b,sub] = texts; 1460 | 1461 | const af = a.substr(0,1) 1462 | a = a.substr(1) 1463 | 1464 | const bf = b.substr(0,1) 1465 | b = b.substr(1) 1466 | 1467 | const afCanvas = makeTextCanvas(af); 1468 | ctx.drawImage( 1469 | afCanvas, 1470 | padding * 2, height * 0.06, 1471 | width * 0.3,height * 0.4 1472 | ); 1473 | 1474 | const aCanvas = makeTextSizeDiffCanvas(a,-space); 1475 | ctx.drawImage( 1476 | aCanvas, 1477 | width * 0.3 + padding * 1.5, height * 0.15, 1478 | width * 0.5,height * 0.3 1479 | ) 1480 | 1481 | const bfCanvas = makeTextCanvas(bf); 1482 | ctx.drawImage( 1483 | bfCanvas, 1484 | padding * 2, height * 0.55, 1485 | width * 0.3,height * 0.4 1486 | ); 1487 | const bCanvas = makeTextSizeDiffCanvas(b,-space); 1488 | ctx.drawImage( 1489 | bCanvas, 1490 | width * 0.3 + padding * 1.5, height * 0.65, 1491 | width * 0.5,height * 0.3 1492 | ) 1493 | 1494 | if(sub){ 1495 | const subCanvas = makeTextCanvas(sub,-space) 1496 | let subHeight = height * 0.14; 1497 | let subWidth = subHeight / subCanvas.height * subCanvas.width * 0.9; 1498 | 1499 | ctx.drawImage( 1500 | subCanvas, 1501 | width - subWidth - padding * 2, (height) / 2, 1502 | subWidth,subHeight 1503 | ); 1504 | } 1505 | 1506 | } 1507 | }, 1508 | { //两排兜底 1509 | id:'e10', 1510 | title: '第拾話 マグマダイバー', 1511 | inputs:[ 1512 | { 1513 | placeholder:'マグマ', 1514 | minLength:2, 1515 | maxLength:14, 1516 | }, 1517 | { 1518 | placeholder:'ダイバー', 1519 | minLength:2, 1520 | maxLength:14, 1521 | }, 1522 | { 1523 | placeholder:'第拾話', 1524 | minLength:2, 1525 | maxLength:14, 1526 | }, 1527 | ], 1528 | config:{ 1529 | plan:'br', 1530 | noise:true, 1531 | }, 1532 | make:({ 1533 | canvas, 1534 | ctx, 1535 | texts, 1536 | config, 1537 | functions, 1538 | })=>{ 1539 | const { 1540 | width, 1541 | height, 1542 | scale, 1543 | padding, 1544 | space, 1545 | fontColor, 1546 | } = config; 1547 | 1548 | const { 1549 | randOne, 1550 | setCtxConfig, 1551 | makeTextCanvas, 1552 | makeTextSizeDiffCanvas, 1553 | makeLinesCanvas, 1554 | makeLinesDiffSizeCanvas, 1555 | makeVerticalTextCanvas, 1556 | } = functions; 1557 | 1558 | // console.log(/两排托底/,texts); 1559 | if(texts.length){ 1560 | const aCanvas = makeLinesCanvas(texts.slice(0,2),-space * 1.4) 1561 | ctx.drawImage( 1562 | aCanvas, 1563 | width * 0.1, height * 0.05, 1564 | width * 0.85, height * 0.7, 1565 | ) 1566 | } 1567 | 1568 | const sub = texts[2]; 1569 | 1570 | const subCanvas = makeTextCanvas(sub,-space) 1571 | let subHeight = height * 0.19; 1572 | let subWidth = subHeight / subCanvas.height * subCanvas.width; 1573 | 1574 | ctx.drawImage( 1575 | subCanvas, 1576 | width - padding- subWidth,height - padding - subHeight, 1577 | subWidth,subHeight 1578 | ); 1579 | 1580 | } 1581 | }, 1582 | 1583 | 1584 | ] -------------------------------------------------------------------------------- /html/logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itorr/eva-title/f46226ee871b6b9ceb919d5e888ac8a795f5def5/html/logo@2x.png -------------------------------------------------------------------------------- /html/make.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const ios = /iphone|ipad|ipod|ios/i.test(navigator.userAgent); 4 | const isSafari = navigator.vendor && navigator.vendor.indexOf('Apple') > -1 && 5 | navigator.userAgent && 6 | navigator.userAgent.indexOf('CriOS') == -1 && 7 | navigator.userAgent.indexOf('FxiOS') == -1; 8 | const isMobile = document.body.offsetWidth < 700; 9 | // const whiteColor = '#e8e8e8' 10 | const whiteColor = '#e4e0e8' 11 | const blackColor = '#030201' 12 | const orangeColor = 'rgba(255,165,0,.6)' 13 | const orangeColorInverse = 'rgba(255,165,255,.2)' 14 | 15 | let fontWeight = 900; 16 | 17 | let fontFamilyName = 'EVAMatisseClassic' 18 | const engFontFamilyName = `"Times New Roman"` 19 | 20 | let baseFontFamilyName = 'EVA_Matisse_Classic-EB,MatissePro-EB,baseSplit,notdef,SourceHanSerifCN-Heavy,serif'; 21 | 22 | 23 | const defaultOutputRatio = 1.334; 24 | 25 | if(ios){ 26 | fontWeight = 500; 27 | } 28 | // if(ios){ 29 | // fontFamilyName = 'SourceHanSerifHeavy'; 30 | // } 31 | // fontFamilyName = 'SourceHanSerifHeavy'; 32 | 33 | // if(ios || !isChrome){ 34 | // fontFamilyName = 'MatisseProEB' 35 | // } 36 | // fontFamilyName = 'RaglanStdUB' 37 | 38 | const isEngRegex = /^[a-z\s!?:\.()\[\]\{\}]+$/i; 39 | 40 | let startTagsString = '「“(‘(『【《〈'; 41 | let endTagsString = '」”)’)』】》〉'; 42 | 43 | let startTags = startTagsString.split(''); 44 | let endTags = endTagsString.split(''); 45 | 46 | 47 | let startTagsReg = new RegExp(`[${startTags.join('|')}]`,'g'); 48 | let endTagsReg = new RegExp(`[${endTags.join('|')}]`,'g'); 49 | 50 | let StartAndEndTagReg = new RegExp(`([${startTags.join('|')}])|([${endTags.join('|')}])`,'g'); 51 | let StartAndEndTagTestRegex = new RegExp(`([${startTags.join('|')}])|([${endTags.join('|')}])`); 52 | 53 | let startEndTitleTextReg = new RegExp(`^([${startTags.join('|')}])(.+)([${endTags.join('|')}])$`); 54 | 55 | 56 | const randOne = (arr,i)=>{ 57 | if(i !== undefined)return arr[i]; 58 | return arr[Math.floor(Math.random()*arr.length)] 59 | } 60 | const rand = (a,b)=>{ 61 | return a + (b-a) * Math.random() 62 | } 63 | const canvasBoxEl = document.createElement('div'); 64 | canvasBoxEl.className = 'canvas-box'; 65 | document.body.appendChild(canvasBoxEl); 66 | 67 | const createCanvas = _=>{ 68 | const canvas = document.createElement('canvas'); 69 | canvasBoxEl.appendChild(canvas); 70 | return canvas; 71 | } 72 | 73 | const removeCanvas = canvas=>{ 74 | if(canvas.parentNode !== canvasBoxEl) return; 75 | canvasBoxEl.removeChild(canvas); 76 | } 77 | 78 | const rgb2yuv = (r,g,b)=>{ 79 | var y, u, v; 80 | 81 | y = r * .299000 + g * .587000 + b * .114000; 82 | u = r * -.168736 + g * -.331264 + b * .500000 + 128; 83 | v = r * .500000 + g * -.418688 + b * -.081312 + 128; 84 | 85 | y = Math.floor(y); 86 | u = Math.floor(u); 87 | v = Math.floor(v); 88 | 89 | return [y,u,v]; 90 | }; 91 | 92 | const yuv2rgb = (y,u,v)=>{ 93 | var r,g,b; 94 | 95 | r = y + 1.4075 * (v - 128); 96 | g = y - 0.3455 * (u - 128) - (0.7169 * (v - 128)); 97 | b = y + 1.7790 * (u - 128); 98 | 99 | r = Math.floor(r); 100 | g = Math.floor(g); 101 | b = Math.floor(b); 102 | 103 | r = (r < 0) ? 0 : r; 104 | r = (r > 255) ? 255 : r; 105 | 106 | g = (g < 0) ? 0 : g; 107 | g = (g > 255) ? 255 : g; 108 | 109 | b = (b < 0) ? 0 : b; 110 | b = (b > 255) ? 255 : b; 111 | 112 | return [r,g,b]; 113 | }; 114 | const measureText = (ctx,text,fontSize)=>{ 115 | const metrics = ctx.measureText(text); 116 | 117 | const width = Math.ceil(metrics.width); 118 | const height = (metrics.fontBoundingBoxDescent + metrics.fontBoundingBoxAscent) || fontSize; 119 | // || (metrics.actualBoundingBoxDescent + metrics.actualBoundingBoxAscent ) 120 | 121 | // console.log(metrics) 122 | return { 123 | metrics, 124 | width, 125 | height 126 | } 127 | } 128 | 129 | 130 | const zoomCanvas = createCanvas(); 131 | 132 | const make = ({ 133 | outputCanvas = createCanvas(), 134 | canvas = createCanvas(), 135 | texts, 136 | config = {}, 137 | timer = false, 138 | layout 139 | })=>{ 140 | if(timer) console.time(layout.title) 141 | 142 | const ctx = canvas.getContext('2d'); 143 | 144 | let { 145 | shadow = true, 146 | type95 = false, 147 | convolute = false, 148 | noise = false, 149 | plan, 150 | outputRatio = 1.334, 151 | } = config; 152 | 153 | // if(ios) shadow = false; 154 | 155 | 156 | const insetHeight = config.height || 480; 157 | const insetWidth = Math.floor(insetHeight * defaultOutputRatio); 158 | 159 | let renderScale = 2; 160 | 161 | if(type95){ 162 | renderScale = 1; 163 | convolute = true; 164 | } 165 | 166 | // const defaultHeight = 960; 167 | const defaultWidth = insetWidth * renderScale; 168 | const defaultHeight = insetHeight * renderScale; 169 | 170 | 171 | const padding = defaultHeight / 24; 172 | const space = padding / 2; 173 | 174 | const fontSize = defaultHeight / 2; 175 | const fontPadding = fontSize / 48; 176 | // console.log({fontPadding}) 177 | const defaultFontSize = fontSize; 178 | const kataFontSize = Math.floor(fontSize * 0.85); 179 | const kataFontSizeDiff = fontSize - kataFontSize; 180 | 181 | 182 | let width = defaultWidth; 183 | let height = defaultHeight; 184 | 185 | const scale = width / height; 186 | 187 | 188 | // config.width = width; 189 | // config.height = height; 190 | // config.scale = scale; 191 | 192 | canvas.width = width; 193 | canvas.height = height; 194 | 195 | let fontColor = whiteColor 196 | let backgroundColor = blackColor 197 | let shadowColor = orangeColor 198 | 199 | switch(plan){ 200 | case 'wb': 201 | fontColor = blackColor 202 | backgroundColor = whiteColor 203 | shadowColor = orangeColorInverse 204 | break; 205 | case 'br': 206 | fontColor = '#D00' 207 | backgroundColor = '#180000' 208 | shadowColor = 'rgba(255,0,0,.5)' 209 | break; 210 | case 'rw': 211 | fontColor = whiteColor 212 | backgroundColor = '#910b0b' 213 | shadowColor = 'rgba(255,120,120,.7)' 214 | break; 215 | case 'by': 216 | fontColor = '#e77225' 217 | backgroundColor = '#140202' 218 | shadowColor = 'rgba(231,120,0,.5)' 219 | break; 220 | case 'yb': 221 | fontColor = '#140202' 222 | backgroundColor = '#e77205' 223 | shadowColor = 'rgba(231,120,0,.5)' 224 | break; 225 | } 226 | ctx.fillStyle = backgroundColor; 227 | ctx.fillRect( 228 | 0, 0, 229 | width, height 230 | ); 231 | 232 | 233 | const setCtxConfig = (ctx,config = {})=>{ 234 | const { 235 | fontSize = defaultFontSize, 236 | textBaseline = 'middle', 237 | _fontFamilyName = fontFamilyName, 238 | // shadow = true 239 | } = config; 240 | 241 | // console.log({ctx}) 242 | 243 | ctx.font = `${fontWeight} ${fontSize}px ${_fontFamilyName},${baseFontFamilyName}`; 244 | ctx.fillStyle = fontColor 245 | ctx.lineCap = 'round'; 246 | ctx.lineJoin = 'round'; 247 | ctx.textAlign = 'left'; 248 | ctx.textBaseline = textBaseline; 249 | 250 | if(shadow){ 251 | ctx.shadowColor = shadowColor;//'orange'; 252 | ctx.shadowBlur = space * 2; 253 | } 254 | } 255 | 256 | 257 | const makeTextCanvas = (text,letterSpacing = 0,margin = 0)=>{ 258 | const canvas = createCanvas(); 259 | const ctx = canvas.getContext('2d'); 260 | 261 | let fontFamilyName = null; 262 | 263 | if(isEngRegex.test(text)){ 264 | fontFamilyName = engFontFamilyName 265 | } 266 | 267 | canvas.style.letterSpacing = `${letterSpacing}px` 268 | 269 | 270 | setCtxConfig(ctx,{ 271 | fontFamilyName 272 | }) 273 | 274 | let { width , height } = measureText(ctx,text,fontSize); 275 | 276 | width = width - letterSpacing + fontPadding * 2; 277 | height = height + fontPadding * 2; 278 | 279 | canvas.width = width; 280 | canvas.height = height; 281 | 282 | setCtxConfig(ctx,{ 283 | fontFamilyName 284 | }) 285 | 286 | ctx.fillText( 287 | text, 288 | fontPadding, height/2 289 | ); 290 | removeCanvas(canvas) 291 | return canvas 292 | 293 | } 294 | const pointWidthScale = 0.4; 295 | const makeTextSizeDiffCanvas = (text,letterSpacing = 0,firstWord = 1)=>{ 296 | let kataRegex = /[ァ-ヶぁ-ん、]/g; 297 | let kataMatch = text.match(kataRegex); 298 | if(!kataMatch) return makeTextCanvas(text,letterSpacing) 299 | 300 | // console.log(text,kataMatch.length , text.length) 301 | if( (kataMatch.length / text.length) > 0.5 ){ 302 | kataRegex = /[のい的、]/g; 303 | kataMatch = text.match(kataRegex) 304 | // console.log(text,kataMatch.length , text.length) 305 | if(!kataMatch) return makeTextCanvas(text,letterSpacing) 306 | } 307 | 308 | const canvas = createCanvas(); 309 | const ctx = canvas.getContext('2d'); 310 | 311 | canvas.style.letterSpacing = `${letterSpacing}px` 312 | 313 | const mojis = text.split('') 314 | 315 | 316 | let allWidth = fontPadding * 2 - letterSpacing; 317 | mojis.forEach(moji=>{ 318 | 319 | let mojiFontSize = fontSize; 320 | 321 | if(/、/.test(moji)){ 322 | mojiFontSize = fontSize * pointWidthScale 323 | }else if(new RegExp(kataRegex,'').test(moji)){ 324 | mojiFontSize = kataFontSize; 325 | } 326 | 327 | 328 | allWidth += mojiFontSize + letterSpacing; 329 | }) 330 | 331 | const width = allWidth; 332 | const height = fontSize + fontPadding * 2; 333 | 334 | canvas.width = width 335 | canvas.height = height 336 | 337 | 338 | let left = fontPadding; 339 | 340 | mojis.forEach((moji,index)=>{ 341 | 342 | const isKata = new RegExp(kataRegex,'').test(moji); 343 | let _fontSize = isKata?kataFontSize:fontSize; 344 | 345 | // if(index === 0 && firstWord !== 1){ 346 | // _fontSize *= firstWord; 347 | // } 348 | 349 | setCtxConfig(ctx,{ 350 | textBaseline:'bottom', 351 | fontSize: _fontSize 352 | }); 353 | 354 | ctx.fillText( 355 | moji, 356 | left, height 357 | ); 358 | if(/、/.test(moji)){ 359 | _fontSize = fontSize * pointWidthScale 360 | } 361 | left += _fontSize + letterSpacing; 362 | }) 363 | removeCanvas(canvas); 364 | return canvas 365 | } 366 | const makeLinesCanvas = (texts,letterSpacing = 0)=>{ 367 | const canvas = createCanvas(); 368 | const ctx = canvas.getContext('2d'); 369 | 370 | setCtxConfig(ctx); 371 | let allWidth = 0; 372 | let allHeight = 0; 373 | const lines = texts.map(text=>{ 374 | 375 | const _canvas = makeTextCanvas(text) 376 | const width = _canvas.width; 377 | const height = _canvas.height; 378 | 379 | const top = allHeight; 380 | allHeight += height + letterSpacing; 381 | 382 | if(width > allWidth){ 383 | allWidth = width; 384 | } 385 | return { 386 | image:_canvas, 387 | text, 388 | width, 389 | height, 390 | top 391 | } 392 | }); 393 | canvas.width = allWidth 394 | canvas.height = allHeight - letterSpacing 395 | lines.forEach(line=>{ 396 | ctx.drawImage( 397 | line.image, 398 | 0,line.top, 399 | line.width,line.height 400 | ) 401 | }) 402 | removeCanvas(canvas); 403 | return canvas; 404 | } 405 | const makeLinesDiffSizeCanvas = (texts,config = {})=>{ 406 | const { 407 | lineHeight = 1, 408 | } = config 409 | const canvas = createCanvas(); 410 | const ctx = canvas.getContext('2d'); 411 | 412 | setCtxConfig(ctx,{ 413 | // fontFamilyName:'Helvetica' 414 | }); 415 | let allWidth = 0; 416 | let allHeight = 0; 417 | let maxHeight = 0; 418 | let lines = texts.map(text=>{ 419 | const _canvas = makeTextCanvas(text) 420 | const width = _canvas.width; 421 | const height = _canvas.height; 422 | 423 | // const top = allHeight; 424 | // allHeight += height + letterSpacing; 425 | if(height > maxHeight){ 426 | maxHeight = height; 427 | } 428 | 429 | if(width > allWidth){ 430 | allWidth = width; 431 | } 432 | return { 433 | image:_canvas, 434 | text, 435 | width, 436 | height, 437 | } 438 | }); 439 | let letterSpacing = 0; 440 | lines = lines.map(o=>{ 441 | const scale = o.width / allWidth 442 | 443 | if(scale < 0.99){ 444 | o.width = o.width * 0.7 445 | o.height = o.height * 0.7 446 | } 447 | letterSpacing = (lineHeight - 1) * o.height; 448 | 449 | const top = allHeight; 450 | allHeight += o.height + letterSpacing; 451 | 452 | o.top = top; 453 | return o 454 | }); 455 | 456 | canvas.width = allWidth; 457 | canvas.height = allHeight - letterSpacing; 458 | lines.forEach(line=>{ 459 | ctx.drawImage( 460 | line.image, 461 | 0,line.top, 462 | line.width,line.height 463 | ) 464 | }) 465 | removeCanvas(canvas) 466 | return canvas; 467 | } 468 | 469 | 470 | const makeVerticalTextCanvas = (text,letterSpacing = 0)=>{ 471 | const canvas = createCanvas(); 472 | const ctx = canvas.getContext('2d'); 473 | 474 | const config = { 475 | textBaseline:'top', 476 | }; 477 | 478 | if(isEngRegex.test(text)){ 479 | config.fontFamilyName = engFontFamilyName; 480 | } 481 | 482 | setCtxConfig(ctx,config); 483 | 484 | text = text.trim(); 485 | 486 | if(/^[a-zA-Z][a-z ]+$/.test(text)){ 487 | let { width , height } = measureText(ctx,text,fontSize); 488 | 489 | width = width - letterSpacing; 490 | height = height; 491 | 492 | canvas.width = height 493 | canvas.height = width 494 | setCtxConfig(ctx,config); 495 | 496 | ctx.save() 497 | ctx.translate(height,0) 498 | ctx.rotate(90 * Math.PI / 180); 499 | // ctx.translate(-width,-width) 500 | 501 | ctx.fillText( 502 | text, 503 | 0,0 504 | ); 505 | ctx.restore() 506 | return canvas; 507 | } 508 | 509 | const mojis = text.split('') 510 | const lineHeight = fontSize + letterSpacing 511 | const width = fontSize 512 | const height = mojis.length * lineHeight - letterSpacing 513 | 514 | canvas.width = width 515 | canvas.height = height 516 | 517 | setCtxConfig(ctx,config); 518 | ctx.save() 519 | ctx.textAlign = 'center'; 520 | ctx.textBaseline = 'middle'; 521 | 522 | mojis.forEach((moji,index)=>{ 523 | ctx.save() 524 | ctx.translate(width/2, index * lineHeight + fontSize / 2) 525 | if(StartAndEndTagTestRegex.test(moji)|| /ー/.test(moji)){ 526 | ctx.rotate(90 * Math.PI / 180); 527 | } 528 | ctx.fillText( 529 | moji, 530 | 0,0 531 | ); 532 | ctx.restore() 533 | }) 534 | removeCanvas(canvas) 535 | return canvas 536 | 537 | } 538 | 539 | 540 | layout.make({ 541 | canvas, 542 | ctx, 543 | texts, 544 | config: { 545 | width, 546 | height, 547 | scale, 548 | padding, 549 | fontSize, 550 | fontColor, 551 | backgroundColor, 552 | space, 553 | }, 554 | functions: { 555 | randOne, 556 | setCtxConfig, 557 | makeTextCanvas, 558 | makeTextSizeDiffCanvas, 559 | makeLinesCanvas, 560 | makeLinesDiffSizeCanvas, 561 | makeVerticalTextCanvas, 562 | } 563 | }); 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | const blurFunc = _=>{ 572 | ctx.drawImage( 573 | canvas, 574 | 0,0,width,height, 575 | 0.5,0.5,width,height 576 | ) 577 | ctx.drawImage( 578 | canvas, 579 | 0,0,width,height, 580 | -0.5,-0.5,width,height 581 | ) 582 | } 583 | if(config.blur){ 584 | 585 | const zoom = 1.4; 586 | if(zoom !== 1){ 587 | const zoomWidth = width / zoom; 588 | const zoomheight = height / zoom; 589 | 590 | zoomCanvas.width = zoomWidth; 591 | zoomCanvas.height = zoomheight; 592 | const zoomCtx = zoomCanvas.getContext('2d'); 593 | 594 | zoomCtx.drawImage( 595 | canvas, 596 | 0,0,width,height, 597 | 0,0,zoomWidth,zoomheight, 598 | ) 599 | ctx.drawImage( 600 | zoomCanvas, 601 | 0,0,zoomWidth,zoomheight, 602 | 0,0,width,height 603 | ) 604 | } 605 | 606 | let i = 1//config.blur; 607 | while(i--){ 608 | blurFunc(); 609 | } 610 | } 611 | 612 | const outLine = false; 613 | 614 | if(outLine){ 615 | const outLineCanvas = createCanvas(); 616 | const outLineCtx = outLineCanvas.getContext('2d'); 617 | 618 | const outLineZoom = 4; 619 | const outLineWidth = width / outLineZoom; 620 | const outLineheight = height / outLineZoom; 621 | 622 | outLineCanvas.width = outLineWidth 623 | outLineCanvas.height = outLineheight 624 | 625 | outLineCtx.drawImage( 626 | canvas, 627 | 0,0,width,height, 628 | 0,0,outLineWidth,outLineheight 629 | ); 630 | ctx.globalAlpha = 0.3; 631 | ctx.drawImage( 632 | outLineCanvas, 633 | 0,0,outLineWidth,outLineheight, 634 | 0,0,width,height, 635 | ); 636 | } 637 | 638 | 639 | 640 | let outputHeight = insetHeight; 641 | let outputWidth = insetWidth; 642 | 643 | let insetLeft = 0; 644 | let insetTop = 0; 645 | 646 | const renderRatio = width / height; 647 | 648 | 649 | if(outputRatio > renderRatio){ 650 | outputWidth = Math.floor(outputHeight * outputRatio); 651 | insetLeft = (outputWidth - insetWidth) / 2; 652 | }else{ 653 | outputHeight = Math.floor(outputWidth / outputRatio); 654 | insetTop = (outputHeight - insetHeight) / 2; 655 | } 656 | 657 | 658 | // const outputCanvas = createCanvas(); 659 | const outputCtx = outputCanvas.getContext('2d'); 660 | 661 | outputCanvas.width = outputWidth 662 | outputCanvas.height = outputHeight 663 | 664 | outputCtx.fillStyle = backgroundColor; 665 | outputCtx.fillRect( 666 | 0, 0, 667 | outputWidth, outputHeight 668 | ); 669 | 670 | outputCtx.drawImage( 671 | canvas, 672 | 0,0, 673 | width,height, 674 | insetLeft,insetTop, 675 | insetWidth,insetHeight, 676 | ) 677 | 678 | if(convolute || noise){ 679 | 680 | let pixel = outputCtx.getImageData(0,0,outputWidth,outputHeight); 681 | // let pixelData = pixel.data; 682 | 683 | // for(let i = 0;i < pixelData.length;i += 4){ 684 | // let yuv = rgb2yuv( 685 | // pixelData[i ], 686 | // pixelData[i+1], 687 | // pixelData[i+2], 688 | // ); 689 | // // UV 漂移 690 | // yuv = UVshifting(yuv,config); 691 | 692 | // pixelData[i ] = yuv[0]; 693 | // pixelData[i+1 - shiftUPixel ] = yuv[1]; 694 | // pixelData[i+2 - shiftVPixel ] = yuv[2]; 695 | // } 696 | if(noise){ 697 | let pixelData = pixel.data; 698 | let seed = 18; 699 | // console.log({seed}) 700 | for(let i = 0;i < pixelData.length;i += 4){ 701 | let l = pixelData[i] * .299000 + pixelData[i+1] * .587000 + pixelData[i+2] * .114000; 702 | let s = Math.round(rand(-seed,seed)) * (l/255 - 0.5); 703 | s = Math.floor(s) 704 | // console.log({s}) 705 | 706 | pixelData[i ] = pixelData[i ] + s; 707 | pixelData[i+1 ] = pixelData[i+1 ] + s; 708 | pixelData[i+2 ] = pixelData[i+2 ] + s; 709 | } 710 | } 711 | 712 | 713 | if(convolute){ 714 | let a = 0.35; 715 | pixel = convolutePixel( 716 | pixel, 717 | [ 718 | 0, -a, 0, 719 | -a, 1 +a*2, a, 720 | 0, -a, 0 721 | ], 722 | outputCtx 723 | ); 724 | 725 | blurFunc(); 726 | } 727 | // pixelData = pixel.data; 728 | 729 | // for(let i = 0;i < pixelData.length;i += 4){ 730 | 731 | // let _rgb = yuv2rgb( 732 | // pixelData[i], 733 | // pixelData[i+1], 734 | // pixelData[i+2], 735 | // ); 736 | 737 | // pixelData[i ] = _rgb[0]; 738 | // pixelData[i+1 ] = _rgb[1]; 739 | // pixelData[i+2 ] = _rgb[2]; 740 | // } 741 | 742 | 743 | outputCtx.putImageData(pixel,0,0); 744 | 745 | } 746 | 747 | 748 | if(type95){ 749 | // const pixel = outputCtx.getImageData(0,0,outputWidth,outputHeight); 750 | // const pixelData = pixel.data; 751 | 752 | // for(let i = 0;i < pixelData.length;i += 4){ 753 | // const yuv = rgb2yuv( 754 | // pixelData[i ], 755 | // pixelData[i+1], 756 | // pixelData[i+2], 757 | // ); 758 | 759 | // yuv[0] = Math.max(yuv[0],50); 760 | 761 | // const _rgb = yuv2rgb(yuv); 762 | 763 | // pixelData[i ] = _rgb[0]; 764 | // pixelData[i+1 ] = _rgb[1]; 765 | // pixelData[i+2 ] = _rgb[2]; 766 | // } 767 | 768 | // outputCtx.putImageData(pixel,0,0); 769 | 770 | 771 | outputCtx.fillStyle = 'rgba(108,130,108,.14)'; 772 | outputCtx.fillRect( 773 | 0, 0, 774 | outputWidth,outputHeight 775 | ); 776 | } 777 | 778 | if(timer) console.timeEnd(layout.title) 779 | 780 | removeCanvas(canvas); 781 | removeCanvas(outputCanvas); 782 | 783 | return outputCanvas 784 | } 785 | 786 | 787 | 788 | 789 | const convolutePixel = (pixels,weights,ctx)=>{ 790 | const side = Math.round(Math.sqrt(weights.length)); 791 | const halfSide = Math.floor(side/2); 792 | 793 | const src = pixels.data; 794 | const sw = pixels.width; 795 | const sh = pixels.height; 796 | 797 | const w = sw; 798 | const h = sh; 799 | const output = ctx.createImageData(w, h); 800 | const dst = output.data; 801 | 802 | 803 | for (let y=0; y{ 30 | // document.body.appendChild(canvas) 31 | const ctx = canvas.getContext('2d') 32 | 33 | const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height) 34 | 35 | const imgBuf = makeBmp(canvas.width, canvas.height) 36 | imgBuf.set(imgData.data) 37 | 38 | const blob = new Blob([imgBuf.buffer], { 39 | type: 'image/bmp', 40 | }) 41 | const url = URL.createObjectURL(blob) 42 | return url 43 | } -------------------------------------------------------------------------------- /html/transform-func.js: -------------------------------------------------------------------------------- 1 | 2 | let sc = '每你妳专业丛东丝丢两严丧临为丽举义乌乐乔习书买亏亚产亩亲亵亿仅仑仓仪们优伙伛伞伟传伤伥伦伧伪伫佣佥侣侥侦侧侨侩侪侬俦俨俩俪俭债倾偻偿傥傧储傩兑兰关兴养兽冈军农冯冻净减凑凤凫凯击凿刍划刘则刚创删别刭剀剂剑剧劝办务动劲劳势勋匮华协单卖卢卤卧卫厅历压厌厕厢县叆叇发变叠叹吓吕吨吴呒呓呗员呙呜咔咛哌哑哕哗哙唤啧啬啭啮啰啸喷嗫嗳嘤噜噼嚣嚯团园围图圆圹场块坚坛坝坞坟坠垄垅垆垒垦垧垩埘埚堑墙壳壶壸处备复头夺奁奂奋奖妆妇妈妪娅娇娱娲娴婴婵媪嫔嫱孙孪宁实宠审宪宫宽宾对寻导尘尧层屿岁岂岖岗岘岚岛岭峣峤峥崄崭嵘嵚嵴巩币帅师帏帐帜带帧帱帻帼幂庆庐庑库应庞废廪开异张弹归录彻徕忆忏忧忾怀态怂怃怅怆总怼怿恳恶恸恺恻恼恽悫悬悭悯惊惩惫惬惭惮惯愤慑慭懑懒懔戋戏战戬户执扩扪扫扬扰抚抟抡抢护报拟拣拥拨择挚挝挞挠挤挥捞损捡换捣掷掺揽搀搁搅摄摅摆摇摈摊撄撷擞攒敌敛斋斩时旷旸昙显晓晔晕晖暂暧术杀杂杨极构枞枣枥枨枪枫枭柜柠柽栀标栈栉栋栌栎栏树样栾桠桡桢桤桥桦桩检棂椠椭榄榇榈槚槛槟槠樯樱橹橼欢歼殁殇殒殚殡毁毂毕毙氲汇汉污汤汹沟沤沥沦沧沨沵泞泷泸泻泼泽泾洼浃浆浇浊测济浏浑浒浓浔涟涡涣涤润涧涨涩渍渎渐渔渖溃溅滚满滢滤滥滨滩漤潇潋澜濑濒灏灭灵灿炀炖炜炼炽烁烂烛烦烧烨烬热焕焘煅煳熘爱爷牍牵牺犊犟犷狈狞狮狯狱猎猕猬獭玑玙玛玮环现珐珑珰珲琏琐琼璎瓒电畅疗疟疠疡疮疯痈痉痨痫瘗瘫瘿癞癣癫皑皱皲盏盐监盘睁睑瞒矫矶矾矿码砖砗砚砻砾础硕硖硗硙确硷碛磙祯祸秃秆种积秽秾稆稣稳穑穷窍窑窜窝窥窦窭竖竞笃笕笺笼筚筛筹签简箓箦箧箩箫篑篓篮篱籁籴粜粝粪糁紧纟纠纡红纣纤纥约级纨纩纪纫纬纭纮纯纰纱纲纳纴纵纶纷纸纹纺纻纽纾线绀绁练组绅细织终绊绍绎经绐绒结绔绕绗绘给绚绛络绞统绠绢绣绤绥绦继绩绪绫续绮绯绰绳维绵绶绷绸绹绻综绽绾绿缀缁缄缅缆缇缈缉缊缋缎缐缑缒缓缔缕编缗缘缙缚缛缝缟缠缢缣缤缥缦缧缨缩缪缬缭缮缯缲缴缵罂罗罚罢罴羁翘翚耸聂聋职聍联聩聪肃肠肤肷肾肿胀胁胜胧胪胫胶脍脐脑脓脔脸腘腭腻腼腽腾膑舆舣舰舱舻艰艳艺节芜苇苌苍苎苏茏茑茔茕茧荆荚荛荜荞荟荠荡荣荤荥荦荧荨荩荪荫药莲莳莴获莸莹莺莼萝萤营萦萧萨蓝蓟蓣蓦蔷蔺蔼蕴藓虏虑虾虿蚀蚁蚝蚬蛊蛏蛰蛱蛲蜕蜗蜡蝇蝼蝾衅衔补衬衮袄袅袆袜袭裈褛褴襕见观规觅视觇览觉觊觋觌觎觏觐觞誊讠计订讣认讥讦讧讨让讪讫训议讯记讱讲讳讴讵讶讷许讹论讼讽设访诀证诂诃评诅识诈诉诊诋词诏译诒诔试诖诗诘诙诚诛话诞诟诠诡询诣诤该详诧诨诫诬语诮误诰诱诲诳说诵请诸诹诺读诽课谀谁调谄谅谆谈谊谋谌谍谏谐谑谒谓谔谕谗谘谙谚谛谜谝谟谠谡谢谣谤谥谦谧谨谩谪谬谭谮谱谲谳谴谵谶贝贞负贡财责贤败货质贩贪贫贬购贮贯贰贱贲贳贴贵贷贸费贺贻贼贽贾贿赀赁赂赃资赆赈赉赋赌赍赎赏赐赑赒赓赔赖赘赙赚赛赜赞赟赠赡赢赣赪赵趋跃跄跞跷跸跻踌踬踯蹑蹒躏车轧轨轩轪轫转轭轮软轰轲轳轴轵轶轸轹轻轼载轾轿辂较辄辅辆辇辈辉辍辎辏辐辑输辔辕辖辗辘辙辩辫边辽达迁过运还这进远违连迟迳选逊递逻遗邓邝邮邹邻郄郑郦郸酝酦酱酿释鉴銮錾针钉钊钍钏钐钓钗钝钞钟钢钣钤钥钦钧钩钮钰钱钲钳钴钶钹钺钻钾钿铁铂铃铄铅铆铉铊铍铎铗铙铛铜铠铢铣铤铦铨铫铭铮铰铳银铸铺铻链铿销锁锄锅锈锋锐锓锖错锚锜锟锡锢锣锤锥锦锬锭键锯锱锲锳锴锵锷锹锺锻锽锾镀镂镇镈镊镌镐镒镕镖镛镜镝镞镠镡镢镣镦镫镮镱镴镶长门闩闪闭问闯闰闲间闵闷闸闹闺闻闼闽闾阀阁阃阄阅阇阈阉阋阍阎阏阐阑阒阔阖阙队阳阴阵阶际陆陇陈陉陕陨险隐隽难雏雠雳雾霁霭靓靥鞑韦韩韪韫韬页顶顷项顺须顼顽顾顿颀颁颂颃预颅领颇颈颉颊颍颐频颓颔颕颖颗题颙颚颛颜额颞颠颡颢颤颥颦颧风飏飒飓飔飘飙飚飞飨餍饥饦饧饨饫饬饭饮饯饰饱饲饴饵饶饷饺饼饿馀馁馅馆馈馏馑馒馔马驭驮驯驰驱驲驳驴驶驷驹驺驻驼驽驾驿骀骁骂骄骅骆骇骈骉骊骋验骍骎骏骐骑骓骖骗骘骙骚骛骝骞骟骠骡骤骥骧髅鬓魇魉鱼鲁鲂鲆鲇鲈鲊鲋鲍鲑鲔鲙鲛鲜鲞鲠鲣鲤鲧鲨鲩鲫鲬鲭鲰鲱鲲鲳鲵鲶鲷鲸鲹鲻鲽鳀鳁鳃鳄鳅鳆鳇鳌鳍鳏鳔鳕鳗鳞鳟鳢鳣鸟鸠鸢鸣鸥鸦鸨鸩鸪鸫鸬鸭鸯鸰鸱鸳鸴鸵鸷鸺鸽鸾鸿鹀鹂鹃鹄鹅鹈鹉鹊鹍鹎鹏鹑鹔鹗鹘鹜鹞鹟鹡鹢鹤鹥鹦鹧鹩鹪鹫鹬鹭鹰鹳黉黡黩黾齐齑齿龀龂龃龄龆龈龉龊龋龌龙龚龛龟尝闹钟闲脏为发饥叹复炼掷鉴获唿呢啦值吧响类嗎查說说說卡晚儍雞!?'; 3 | let tc = '毎袮袮專業叢東絲丟兩嚴喪臨為麗舉義烏樂喬習書買虧亞產畝親褻億僅侖倉儀們優夥傴傘偉傳傷倀倫傖偽佇傭僉侶僥偵側僑儈儕儂儔儼倆儷儉債傾僂償儻儐儲儺兌蘭關興養獸岡軍農馮凍淨減湊鳳鳧凱擊鑿芻劃劉則剛創刪別剄剴劑劍劇勸辦務動勁勞勢勳匱華協單賣盧鹵臥衛廳曆壓厭廁廂縣靉靆發變疊歎嚇呂噸吳嘸囈唄員咼嗚哢嚀呱啞噦嘩噲喚嘖嗇囀齧囉嘯噴囁噯嚶嚕劈囂謔團園圍圖圓壙場塊堅壇壩塢墳墜壟壟壚壘墾坰堊塒堝塹牆殼壺壼處備複頭奪奩奐奮獎妝婦媽嫗婭嬌娛媧嫻嬰嬋媼嬪嬙孫孿寧實寵審憲宮寬賓對尋導塵堯層嶼歲豈嶇崗峴嵐島嶺嶢嶠崢嶮嶄嶸嶔脊鞏幣帥師幃帳幟帶幀幬幘幗冪慶廬廡庫應龐廢廩開異張彈歸錄徹徠憶懺憂愾懷態慫憮悵愴總懟懌懇惡慟愷惻惱惲愨懸慳憫驚懲憊愜慚憚慣憤懾憖懣懶懍戔戲戰戩戶執擴捫掃揚擾撫摶掄搶護報擬揀擁撥擇摯撾撻撓擠揮撈損撿換搗擲摻攬攙擱攪攝攄擺搖擯攤攖擷擻攢敵斂齋斬時曠暘曇顯曉曄暈暉暫曖術殺雜楊極構樅棗櫪棖槍楓梟櫃檸檉梔標棧櫛棟櫨櫟欄樹樣欒椏橈楨榿橋樺樁檢欞槧橢欖櫬櫚檟檻檳櫧檣櫻櫓櫞歡殲歿殤殞殫殯毀轂畢斃氳彙漢汙湯洶溝漚瀝淪滄渢濔濘瀧瀘瀉潑澤涇窪浹漿澆濁測濟瀏渾滸濃潯漣渦渙滌潤澗漲澀漬瀆漸漁瀋潰濺滾滿瀅濾濫濱灘濫瀟瀲瀾瀨瀕灝滅靈燦煬燉煒煉熾爍爛燭煩燒燁燼熱煥燾煆糊溜愛爺牘牽犧犢強獷狽獰獅獪獄獵獼蝟獺璣璵瑪瑋環現琺瓏璫琿璉瑣瓊瓔瓚電暢療瘧癘瘍瘡瘋癰痙癆癇瘞癱癭癩癬癲皚皺皸盞鹽監盤睜瞼瞞矯磯礬礦碼磚硨硯礱礫礎碩硤磽磑確鹼磧滾禎禍禿稈種積穢穠穭穌穩穡窮竅窯竄窩窺竇窶豎競篤筧箋籠篳篩籌簽簡籙簀篋籮簫簣簍籃籬籟糴糶糲糞糝緊糸糾紆紅紂纖紇約級紈纊紀紉緯紜紘純紕紗綱納紝縱綸紛紙紋紡紵紐紓線紺絏練組紳細織終絆紹繹經紿絨結絝繞絎繪給絢絳絡絞統綆絹繡綌綏絛繼績緒綾續綺緋綽繩維綿綬繃綢綯綣綜綻綰綠綴緇緘緬纜緹緲緝縕繢緞線緱縋緩締縷編緡緣縉縛縟縫縞纏縊縑繽縹縵縲纓縮繆纈繚繕繒繰繳纘罌羅罰罷羆羈翹翬聳聶聾職聹聯聵聰肅腸膚膁腎腫脹脅勝朧臚脛膠膾臍腦膿臠臉膕齶膩靦膃騰臏輿艤艦艙艫艱豔藝節蕪葦萇蒼苧蘇蘢蔦塋煢繭荊莢蕘蓽蕎薈薺蕩榮葷滎犖熒蕁藎蓀蔭藥蓮蒔萵獲蕕瑩鶯蓴蘿螢營縈蕭薩藍薊蕷驀薔藺藹蘊蘚虜慮蝦蠆蝕蟻蠔蜆蠱蟶蟄蛺蟯蛻蝸蠟蠅螻蠑釁銜補襯袞襖嫋褘襪襲褌褸襤襴見觀規覓視覘覽覺覬覡覿覦覯覲觴謄訁計訂訃認譏訐訌討讓訕訖訓議訊記訒講諱謳詎訝訥許訛論訟諷設訪訣證詁訶評詛識詐訴診詆詞詔譯詒誄試詿詩詰詼誠誅話誕詬詮詭詢詣諍該詳詫諢誡誣語誚誤誥誘誨誑說誦請諸諏諾讀誹課諛誰調諂諒諄談誼謀諶諜諫諧謔謁謂諤諭讒諮諳諺諦謎諞謨讜謖謝謠謗諡謙謐謹謾謫謬譚譖譜譎讞譴譫讖貝貞負貢財責賢敗貨質販貪貧貶購貯貫貳賤賁貰貼貴貸貿費賀貽賊贄賈賄貲賃賂贓資贐賑賚賦賭齎贖賞賜贔賙賡賠賴贅賻賺賽賾讚贇贈贍贏贛赬趙趨躍蹌躒蹺蹕躋躊躓躑躡蹣躪車軋軌軒軑軔轉軛輪軟轟軻轤軸軹軼軫轢輕軾載輊轎輅較輒輔輛輦輩輝輟輜輳輻輯輸轡轅轄輾轆轍辯辮邊遼達遷過運還這進遠違連遲逕選遜遞邏遺鄧鄺郵鄒鄰郤鄭酈鄲醞醱醬釀釋鑒鑾鏨針釘釗釷釧釤釣釵鈍鈔鍾鋼鈑鈐鑰欽鈞鉤鈕鈺錢鉦鉗鈷鈳鈸鉞鑽鉀鈿鐵鉑鈴鑠鉛鉚鉉鉈鈹鐸鋏鐃鐺銅鎧銖銑鋌銛銓銚銘錚鉸銃銀鑄鋪鋙鏈鏗銷鎖鋤鍋鏽鋒銳鋟錆錯錨錡錕錫錮鑼錘錐錦錟錠鍵鋸錙鍥鍈鍇鏘鍔鍬鍾鍛鍠鍰鍍鏤鎮鎛鑷鐫鎬鎰鎔鏢鏞鏡鏑鏃鏐鐔钁鐐鐓鐙鐶鐿鑞鑲長門閂閃閉問闖閏閑間閔悶閘鬧閨聞闥閩閭閥閣閫鬮閱闍閾閹鬩閽閻閼闡闌闃闊闔闕隊陽陰陣階際陸隴陳陘陝隕險隱雋難雛讎靂霧霽靄靚靨韃韋韓韙韞韜頁頂頃項順須頊頑顧頓頎頒頌頏預顱領頗頸頡頰潁頤頻頹頷頴穎顆題顒顎顓顏額顳顛顙顥顫顬顰顴風颺颯颶颸飄飆飆飛饗饜饑飥餳飩飫飭飯飲餞飾飽飼飴餌饒餉餃餅餓餘餒餡館饋餾饉饅饌馬馭馱馴馳驅馹駁驢駛駟駒騶駐駝駑駕驛駘驍罵驕驊駱駭駢驫驪騁驗騂駸駿騏騎騅驂騙騭騤騷騖騮騫騸驃騾驟驥驤髏鬢魘魎魚魯魴鮃鯰鱸鮓鮒鮑鮭鮪鱠鮫鮮鯗鯁鰹鯉鯀鯊鯇鯽鯒鯖鯫鯡鯤鯧鯢鯰鯛鯨鯵鯔鰈鯷鰮鰓鱷鰍鰒鰉鼇鰭鰥鰾鱈鰻鱗鱒鱧鱣鳥鳩鳶鳴鷗鴉鴇鴆鴣鶇鸕鴨鴦鴒鴟鴛鴬鴕鷙鵂鴿鸞鴻鵐鸝鵑鵠鵝鵜鵡鵲鶤鵯鵬鶉鷫鶚鶻鶩鷂鶲鶺鷁鶴鷖鸚鷓鷯鷦鷲鷸鷺鷹鸛黌黶黷黽齊齏齒齔齗齟齡齠齦齬齪齲齷龍龔龕龜嘗鬨鐘閒臟爲髮飢嘆復鍊攫鑑穫呼昵拉値叭響類麼査説説説卞晩傻鸡!?'; 4 | 5 | 6 | 7 | // scArr = sc.split(''); 8 | // tcArr = tc.split(''); 9 | // scmm=[]; 10 | // tcmm=[]; 11 | // nsc = []; 12 | // ntc = []; 13 | // tcArr.forEach((m,i)=>{ 14 | // // console.log(m,i) 15 | // if(!EVAMatisseClassicMojis.includes(m)){ 16 | // scmm.push(scArr[i]); 17 | // tcmm.push(tcArr[i]); 18 | // }else{ 19 | // nsc.push(scArr[i]); 20 | // ntc.push(tcArr[i]); 21 | // } 22 | // }); 23 | 24 | // // console.log(/diff/,scArr.length - sc.length,tcmm.length,scArr.join(''),tcArr.join('')); 25 | // console.log(/diff/,tcmm.length); 26 | // console.log(`let sc = '${nsc.join('')}';`); 27 | // console.log(`let tc = '${ntc.join('')}';`); 28 | // console.log(`// let scmm = '${scmm.join('')}';`); 29 | // console.log(`// let tcmm = '${tcmm.join('')}';`); 30 | 31 | 32 | 33 | // // 找未对应上的文字 34 | // scArr = sc.split(''); 35 | // tcArr = tc.split(''); 36 | // scArr.forEach((m,i)=>{ 37 | // if(!EVAMatisseClassicMojis.includes(m)){ 38 | // // console.log(m); 39 | // if(!EVAMatisseClassicMojis.includes(tcArr[i])){ 40 | // console.log(/繁体也没有/,m,tcArr[i]); 41 | // } 42 | // } 43 | // }); 44 | 45 | 46 | // 丟 实际可以显示 但是 过不去 fontmin 47 | 48 | const transformFunc = { 49 | 1(cc) { 50 | let str = ""; 51 | for (let i = 0; i < cc.length; i++) { 52 | if (cc.charCodeAt(i) > 10000 && tc.indexOf(cc.charAt(i)) != -1) { 53 | str += sc.charAt(tc.indexOf(cc.charAt(i))); 54 | } else { 55 | str += cc.charAt(i); 56 | } 57 | } 58 | return str; 59 | }, 60 | 2(cc) { 61 | let str = ""; 62 | for (let i = 0; i < cc.length; i++) { 63 | if (cc.charCodeAt(i) > 10000 && sc.indexOf(cc.charAt(i)) != -1) { 64 | str += tc.charAt(sc.indexOf(cc.charAt(i))); 65 | } else { 66 | str += cc.charAt(i); 67 | } 68 | } 69 | // str = str.replace('你','你'); 70 | return str; 71 | } 72 | }; 73 | 74 | 75 | // export { 76 | // transformFunc 77 | // } 78 | -------------------------------------------------------------------------------- /html/ui-switch.vue.js: -------------------------------------------------------------------------------- 1 | Vue.component('ui-switch',{ 2 | template: ` 3 | 4 | 5 | 6 | 7 | `, 8 | props:{ 9 | value: Boolean, 10 | color: String 11 | }, 12 | methods:{ 13 | _switch(){ 14 | this.$emit('input',!this.value); 15 | } 16 | } 17 | }) -------------------------------------------------------------------------------- /html/ui-tabs.vue.js: -------------------------------------------------------------------------------- 1 | Vue.component('ui-tabs',{ 2 | template: ` 3 | 4 | `, 5 | props:{ 6 | value: [String,Number], 7 | options: Array 8 | }, 9 | computed: { 10 | _options(){ 11 | const {options} = this; 12 | if(options.constructor === Object){ 13 | return Object.entries(options).map(option=>({ 14 | value: option[0], 15 | text: option[1], 16 | })) 17 | } 18 | return options.map(option=>{ 19 | if(option.constructor === String){ 20 | return { 21 | value: option, 22 | text: option 23 | }; 24 | } 25 | 26 | return option 27 | }) 28 | } 29 | }, 30 | methods:{ 31 | set(v){ 32 | this.$emit('input',v); 33 | } 34 | } 35 | }) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eva-title", 3 | "version": "1.0.0", 4 | "description": "EVA标题生成器", 5 | "main": "index.html", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "itorr", 10 | "license": "ISC", 11 | "dependencies": { 12 | "express": "^4.18.1", 13 | "fontmin": "^0.9.9" 14 | } 15 | } 16 | --------------------------------------------------------------------------------