Repository: jaames/zfont Branch: master Commit: 6981c780303e Files: 16 Total size: 190.0 KB Directory structure: gitextract_sq1u__c3/ ├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── demo/ │ ├── demo.css │ ├── demo.js │ └── index.html ├── deploy.sh ├── dist/ │ ├── zfont.es.js │ └── zfont.js ├── package.json ├── rollup.config.js └── src/ ├── ZdogFont.js ├── ZdogText.js ├── ZdogTextGroup.js └── index.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ # Auto detect text files and perform LF normalization * text=auto ================================================ FILE: .gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* demo/zfont.* # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # nyc test coverage .nyc_output # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # TypeScript v1 declaration files typings/ # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env # parcel-bundler cache (https://parceljs.org/) .cache # next.js build output .next # nuxt.js build output .nuxt # vuepress build output .vuepress/dist # Serverless directories .serverless # FuseBox cache .fusebox/ ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2019 james Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================


Zfont

A text plugin for the Zdog 3D engine! Renders TrueType fonts via Typr.js | jaames.github.io/zfont

Features | Caveats | Demo | Installation | Usage | API | Zdog.Font | Zdog.Text | Zdog.TextGroup | Todo | Building


## Features * Built on top of [Typr.js](https://github.com/photopea/Typr.js), which supports a wide range of .ttf and .otf fonts with speed and grace * Less than 14kB minified and gzipped * No need to worry about waiting for fonts to load; text automatically pops into existence once the font is ready * Includes support for multiline text * Update font, text, color, alignment, etc at any time * Bonus utilities for measuring text, waiting for font load & more! ## Caveats * You have to provide a .ttf to use yourself; it isn't possible to use system fonts * Character range is limited to whichever glyphs are supported by your chosen font, and font stacks/fallbacks aren't supported yet ## Demo A live demo can be found [here](https://jaames.github.io/zfont/), there's also some more in-depth examples on [Codepen](https://codepen.io/collection/DPKGvY/)! ## Installation ### Install with NPM ```bash $ npm install zfont --save ``` If you are using a module bundler like Webpack or Rollup, import Zfont into your project: ```javascript // Using ES6 module syntax import Zfont from 'zfont'; // Using CommonJS modules const Zfont = require('zfont'); ``` ### Using the jsDelivr CDN ```html ``` When manually including the library like this, it will be globally available on `window.Zfont` ### Download and Host Yourself **[Development version](https://raw.githubusercontent.com/jaames/zfont/master/dist/zfont.js)**
Uncompressed at around 75kB, with source comments included **[Production version](https://raw.githubusercontent.com/jaames/zfont/master/dist/zfont.min.js)**
Minified to 45kB Then add it to the `` of your page with a ` ``` ## Usage ### Register Plugin After both Zdog and Zfont have been imported/downloaded, we need to initialize the Zfont plugin. Once it's initialized, the `Zdog.Font`, `Zdog.Text` and `Zdog.TextGroup` classes will be available: ```js Zfont.init(Zdog); ``` ### Hello World (Pssst! If you prefer to dive in, check out the [basic demo over on Codepen](https://codepen.io/rakujira/pen/vqLBwz)) To draw some text in a Zdog scene, first we need to set up a new `Zdog.Font` object with the .ttf url for our desired font, then we can create a new `Zdog.Text` object and add it to the illustration like any other shape: ```js // Initialize Zfont Zfont.init(Zdog); // Create a Zdog illustration let illo = new Zdog.Illustration({ element: '.zdog-canvas' }); // Set up a font to use let myFont = new Zdog.Font({ src: './path/to/font.ttf' }); // Create a text object // This is just a Zdog.Shape object with a couple of extra parameters! new Zdog.Text({ addTo: illo, font: myFont, value: 'Hey, Zdog!', fontSize: 64, color: '#fff' }); // Animation loop function animate() { illo.updateRenderGraph(); requestAnimationFrame(animate); } animate(); ``` ### Multiline Text Both `Zdog.Text` and `Zdog.TextGroup` support multiline text, by inserting a newline character (`\n`) wherever you wish to add a line break: ```js new Zdog.Text({ ... value: 'The quick brown fox\njumps over the\nlazy zdog', }); ``` For better readability you may prefer to use an array of strings for the `value` option. In this case, each string in the array will be treated as a seperate line of text: ```js new Zdog.Text({ ... value: [ 'The quick brown fox' 'jumps over the', 'lazy zdog' ] }); ``` ### Waiting for Fonts to Load In most cases you don't have to worry about waiting for fonts to load, as text objects will magically pop into existence once their font is ready to use. However, the plugin also provides a `Zdog.waitForFonts()` utility function if you need to delay anything until all the fonts in your scene have finished loading. For example, let's modify the animation loop from the previous example so that it doesn't begin until the fonts are ready: ```js // Animation loop function animate() { illo.updateRenderGraph(); requestAnimationFrame(animate); } // Zdog.waitForFonts() returns a Promise which is resolved once all the fonts added to the scene so far have been loaded Zdog.waitForFonts().then(() => { // Once the fonts are done, start the animation loop animate(); }) ``` ## API ### Zdog.Font Represents a font that can be used by an instance of either [`Zdog.Text`](#zdogtext) or [`Zdog.TextGroup`](#zdogtextgroup). ```js let font = new Zdog.Font({ src: './path/to/font.ttf' }) ``` #### Options | Param | Details | Default | |:-----------|:--------|:--------| | `src` | Font URL path. This can be a `.ttf` or `.otf` font, check out the [Typr.js repo](https://github.com/photopea/Typr.js) for more details about font support | `''` | #### Methods ##### `measureText(text, fontSize)` Get the measurements for the specified string `text` when rendered at `fontSize` (measured in pixels), similar to [`Canvas​Rendering​Context2D.measure​Text()`](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/measureText). Returns an object with `width`, `height`, `descender`, `ascender`. ##### `getTextPath(text, fontSize, x=0, y=0, z=0, alignX='left', alignY='bottom')` Returns an array of [Zdog path commands](https://zzz.dog/shapes#shape-path-commands) for the specified string `text`, when rendered at `fontSize` (measured in pixels). * (`x`, `y`, `z`) is the origin point of the path * `alignX` is the horizontal text alignment (equivalent to the CSS `text-align` property); either `"left"`, `"center"` or `"right"`. * `alignY` is the vertical text alignment; either `"top"`, `"middle"` or `"bottom".` ##### `waitForLoad()` Returns a Promise which resolves once this font has finished loading. ### Zdog.Text An object used for rendering text. It inherits everything from the [`Zdog.Shape`](https://zzz.dog/api#shape) class. ```js new Zdog.Text({ addTo: illo, font: font, value: 'Hey, Zdog!', textAlign: 'center', textBaseline: 'middle', color: '#5222ee', stroke: 1, }) ``` #### Options `Zdog.Text` inherits all the options from the [`Zdog.Shape`](https://zzz.dog/api#shape) class, plus a couple of extras: | Param | Details | Default | |:-----------|:--------|:--------| | `font` | [`Zdog.Font`](#zdog-font) to use for this text. This is required. | `null` | | `value` | Text string | `''` | | `fontSize` | Text size, measured in pixels | `64` | | `textAlign`| Horizontal text alignment, equivalent to the CSS `text-align` property. This can be either `'left'`, `'center'` or `'right'` | `'left'` | | `textBaseline`| Vertical text alignment, equivalent to the HTML5 canvas' [`textBaseline`](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textBaseline) property. This can be either `'top'`, `'middle'` or `'bottom'` | `'bottom'` | #### Properties `Zdog.Text` inherits all the properties from the [`Zdog.Shape`](https://zzz.dog/api#shape) class, as well as some extras. All of these properties can be updated at any time and the rendered text will update automatically. ##### `font` The [`Zdog.Font`](#zdog-font) instance being used for this text. ##### `value` Text value as a string. ##### `fontSize` Font size, measured in pixels. ##### `textAlign` Horizontal text alignment, equivalent to the CSS `text-align` property. This can be either `'left'`, `'center'` or `'right'` ##### `textBaseline` Vertical text alignment, equivalent to the HTML5 canvas' [`textBaseline`](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textBaseline) property. This can be either `'top'`, `'middle'` or `'bottom'` ### Zdog.TextGroup This class is very similar to [`Zdog.Text`](#zdog-text), except it acts as a [`Zdog.Group`](https://zzz.dog/api#group) instead, and each text glyph is rendered as its own shape. This is helpful for more advanced use-cases where you need control over each character. ```js new Zdog.TextGroup({ addTo: illo, font: font, value: 'Hey, Zdog!', textAlign: 'center', color: '#5222ee', stroke: 2, }) ``` #### Options `Zdog.TextGroup` inherits all the options from the [`Zdog.Group`](https://zzz.dog/api#group) class, plus a few extras: | Param | Details | Default | |:-----------|:--------|:--------| | `font` | [`Zdog.Font`](#zdog-font) to use for this text. This is required. | `null` | | `value` | Text string | `''` | | `fontSize` | Text size, measured in pixels | `64` | | `textAlign`| Horizontal text alignment, equivalent to the CSS `text-align` property. This can be either `'left'`, `'center'` or `'right'` | `'left'` | | `textBaseline`| Vertical text alignment, equivalent to the HTML5 canvas' [`textBaseline`](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textBaseline) property. This can be either `'top'`, `'middle'` or `'bottom'` | `'bottom'` | | `color` | Text color | `#333` | | `fill` | Text fill | `false` | | `stroke` | Text stroke | `stroke` | #### Properties `Zdog.TextGroup` inherits all the properties from the [`Zdog.Group`](https://zzz.dog/api#group) class, as well as some extras. All of these properties can be updated at any time and the rendered text will update automatically. ##### `font` The [`Zdog.Font`](#zdog-font) instance being used for this text. ##### `value` Text value as a string. ##### `fontSize` Font size, measured in pixels. ##### `textAlign` Horizontal text alignment, equivalent to the CSS `text-align` property. This can be either `'left'`, `'center'` or `'right'` ##### `textBaseline` Vertical text alignment, equivalent to the HTML5 canvas' [`textBaseline`](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textBaseline) property. This can be either `'top'`, `'middle'` or `'bottom'` ##### `color` Text color, equivalent to [`Shape.color`](https://zzz.dog/api#shape-color). Setting this will update the color for all of the group's children. ##### `fill` Text fill, equivalent to [`Shape.fill`](https://zzz.dog/api#shape-fill). Setting this will update the fill for all of the group's children. ##### `stroke` Text stroke, equivalent to [`Shape.stroke`](https://zzz.dog/api#shape-stroke). Setting this will update the stroke for all of the group's children. ### Zdog.waitForFonts Returns a Promise which resolves as soon as all the fonts currently added to the scene are loaded and ready for use. ```js Zdog.waitForFonts().then(function() { // Do something once the font is ready } ``` ## Todo * Google Fonts & Typekit integration? * Support for different text directions, e.g. right-to-left * Support for fallback fonts * Support for color (SVG) fonts ## Building ### Install Dependencies with NPM ```bash $ npm install ``` ### Run Devserver ```bash $ npm start ``` ### Build production files ```bash $ npm run build ``` ---- 2019 [James Daniel](//github.com/jaames) ================================================ FILE: demo/demo.css ================================================ @import url('https://simbo.codes/css-reset-and-normalize/reset-and-normalize.min.css'); @import url('https://fonts.googleapis.com/css?family=Fredoka+One'); body { color: #000032; background: #eef; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; line-height: 1.5; letter-spacing: .015em; font-smoothing: antialiased; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -webkit-text-rendering: optimizeLegibility; -moz-font-smoothing: antialiased; -moz-text-rendering: optimizeLegibility; } h1, h2, h3, h4, h5, h6, .Button { font-family: "Fredoka One", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; } h1 { font-size: 3.5rem; font-weight: bold; } h2 { font-size: 1.25rem; } h3 { font-size: 1.25rem; } a { color: #645bdf; cursor: pointer; text-decoration: none; border-bottom: .08em dashed currentColor; } a:hover { color: #5222ee; } .Page { width: 100%; max-width: 728px; margin: 0 auto; padding: 3rem 12px; } .Header { text-align: center; margin: 4rem 0; } .Section { margin: 2rem 0; } .Section h3 { margin-bottom: 1em; } .Button { display: inline-block; background: #000032; box-shadow: 0 5px 0 -2px #adadc0; color: #fff; padding: 0.5em 1.5em; border-radius: 12px; margin: 0 6px; border: 0; font-size: 1.2rem; transition: transform 0.1s ease-in-out, box-shadow 0.1s ease-in-out; } .Button:hover { color: #6e66e2; transform: scale(1.025); box-shadow: 0 8px 0 -5px #adadc0; } .ButtonGroup { margin: 2rem 0; display: flex; justify-content: center; } .ButtonGroup .Button { /* flex: 1 */ } .FeatureList { padding: 0; list-style-type: none; } .FeatureList .FeatureItem { padding: 8px 0; } @media screen and (min-width: 700px) { .FeatureList { display: flex; margin: 0 -8px; } .FeatureList .FeatureItem { padding: 0 8px; flex: 1; } } .FeatureList .FeatureItem p { /* font-family: */ } .Canvas { width: 100%; border-radius: 12px; background: #5222ee; box-shadow: 0 5px 0 -2px #8d88d6; cursor: pointer; } .Textarea { /* font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; */ border: 2px solid #dde; box-shadow: 0 5px 0 -2px #c3c3dd; border-radius: 12px; display: block; width: 100%; margin-top: 12px; padding: 16px; resize: vertical; } .Footer { text-align: right; margin: 2rem 0; } .GithubCorner { z-index: 10; border: 0; display: block; position: fixed; top: -5px; right: -5px; background: transparent; } .GithubCorner__svg { width: 100px; height: 100px; fill: #000032; transition: transform 0.2s; } .GithubCorner:hover .GithubCorner__svg { -webkit-transform: translate(-5px,5px); transform: translate(-5px,5px); } .GithubCorner .octo-arm, .GithubCorner .octo-body { fill: #eef; } ================================================ FILE: demo/demo.js ================================================ // Register Zfont plugin Zfont.init(Zdog); const textarea = document.getElementById('textarea'); // create illo let illo = new Zdog.Illustration({ element: '.Canvas', dragRotate: true, resize: true, rotate: {x: -0.32, y: 0.64, z: 0} }); // create font let font = new Zdog.Font({ src: './fredokaone.ttf' }); let text = new Zdog.Text({ addTo: illo, font: font, value: textarea.value, fontSize: 48, textAlign: 'center', textBaseline: 'middle', color: '#fff', fill: true, }); // text "shadow" let shadow1 = text.copy({ addTo: illo, translate: {z: -3}, color: '#aab', }); let shadow2 = text.copy({ addTo: illo, translate: {z: -6}, color: '#aab', }); textarea.addEventListener('input', function() { text.value = textarea.value; shadow1.value = textarea.value; shadow2.value = textarea.value; }); // animation loop function animate() { illo.updateRenderGraph(); requestAnimationFrame(animate); } animate(); ================================================ FILE: demo/index.html ================================================ Zfont

Zfont

A text plugin for the Zdog 3D engine

Features

Demo

================================================ FILE: deploy.sh ================================================ #!/usr/bin/env sh # abort on errors set -e # copy dist code into demo cp -a dist/. demo/ # navigate into the build output directory cd demo # create commit git init git add -A git commit -m 'deploy demo page' # force push commit to gh-pages branch git push -f git@github.com:jaames/zfont.git master:gh-pages # navigate to last directory cd - ================================================ FILE: dist/zfont.es.js ================================================ /*! * Zfont v1.2.8 * Text plugin for Zdog * 2019 James Daniel * MIT Licensed * github.com/jaames/zfont */ var Typr={};Typr.parse=function(buff){var bin=Typr._bin;var data=new Uint8Array(buff);var offset=0;var sfnt_version=bin.readFixed(data,offset);offset+=4;var numTables=bin.readUshort(data,offset);offset+=2;var searchRange=bin.readUshort(data,offset);offset+=2;var entrySelector=bin.readUshort(data,offset);offset+=2;var rangeShift=bin.readUshort(data,offset);offset+=2;var tags=["cmap","head","hhea","maxp","hmtx","name","OS/2","post","loca","glyf","kern","CFF ","GPOS","GSUB","SVG "];var obj={_data:data};var tabs={};for(var i=0;i>>i&1)!=0){ num++; } }return num};Typr._lctf.readClassDef=function(data,offset){var bin=Typr._bin;var obj=[];var format=bin.readUshort(data,offset);offset+=2;if(format==1){var startGlyph=bin.readUshort(data,offset);offset+=2;var glyphCount=bin.readUshort(data,offset);offset+=2;for(var i=0;i255){ return -1; }return Typr.CFF.glyphByUnicode(cff,Typr.CFF.tableSE[charcode])};Typr.CFF.readEncoding=function(data,offset,num){var bin=Typr._bin;var array=[".notdef"];var format=data[offset];offset++;if(format==0){var nCodes=data[offset];offset++;for(var i=0;i>4,nib1=b&15;if(nib0!=15){ nibs.push(nib0); }if(nib1!=15){ nibs.push(nib1); }if(nib1==15){ break }}var s="";var chars=[0,1,2,3,4,5,6,7,8,9,".","e","e-","reserved","-","endOfNumber"];for(var i=0;i=gl.xMax||gl.yMin>=gl.yMax){ return null; }if(gl.noc>0){gl.endPts=[];for(var i=0;i>>8;format&=15;if(format==0){ offset=Typr.kern.readFormat0(data,offset,map); }else { throw "unknown kern table format: "+format }}return map};Typr.kern.parseV1=function(data,offset,length,font){var bin=Typr._bin;var version=bin.readFixed(data,offset);offset+=4;var nTables=bin.readUint(data,offset);offset+=4;var map={glyph1:[],rval:[]};for(var i=0;i>>8;format&=15;if(format==0){ offset=Typr.kern.readFormat0(data,offset,map); }else { throw "unknown kern table format: "+format }}return map};Typr.kern.readFormat0=function(data,offset,map){var bin=Typr._bin;var pleft=-1;var nPairs=bin.readUshort(data,offset);offset+=2;var searchRange=bin.readUshort(data,offset);offset+=2;var entrySelector=bin.readUshort(data,offset);offset+=2;var rangeShift=bin.readUshort(data,offset);offset+=2;for(var j=0;j=tab.map.length){ return 0; }return tab.map[code]}else if(tab.format==4){var sind=-1;for(var i=0;icode){ return 0; }var gli=0;if(tab.idRangeOffset[sind]!=0){ gli=tab.glyphIdArray[code-tab.startCount[sind]+(tab.idRangeOffset[sind]>>1)-(tab.idRangeOffset.length-sind)]; }else { gli=code+tab.idDelta[sind]; }return gli&65535}else if(tab.format==12){if(code>tab.groups[tab.groups.length-1][1]){ return 0; }for(var i=0;i-1){ Typr.U._simpleGlyph(gl,path); }else { Typr.U._compoGlyph(gl,font,path); }}};Typr.U._simpleGlyph=function(gl,p){for(var c=0;c65535){ i++; }gls.push(Typr.U.codeToGlyph(font,cc));}var gsub=font["GSUB"];if(gsub==null){ return gls; }var llist=gsub.lookupList,flist=gsub.featureList;var wsep='\n\t" ,.:;!?() ،';var R="آأؤإاةدذرزوٱٲٳٵٶٷڈډڊڋڌڍڎڏڐڑڒړڔڕږڗژڙۀۃۄۅۆۇۈۉۊۋۍۏےۓەۮۯܐܕܖܗܘܙܞܨܪܬܯݍݙݚݛݫݬݱݳݴݸݹࡀࡆࡇࡉࡔࡧࡩࡪࢪࢫࢬࢮࢱࢲࢹૅેૉ૊૎૏ૐ૑૒૝ૡ૤૯஁ஃ஄அஉ஌எஏ஑னப஫஬";var L="ꡲ્૗";for(var ci=0;cirlim){ continue; }var good=true;for(var l=0;lrlim){ continue; }var good=true;for(var l=0;l>1;stack.length=0;haveWidth=true;}else if(v=="o3"||v=="o23"){var hasWidthArg;hasWidthArg=stack.length%2!==0;if(hasWidthArg&&!haveWidth){width=stack.shift()+font.Private.nominalWidthX;}nStems+=stack.length>>1;stack.length=0;haveWidth=true;}else if(v=="o4"){if(stack.length>1&&!haveWidth){width=stack.shift()+font.Private.nominalWidthX;haveWidth=true;}if(open){ Typr.U.P.closePath(p); }y+=stack.pop();Typr.U.P.moveTo(p,x,y);open=true;}else if(v=="o5"){while(stack.length>0){x+=stack.shift();y+=stack.shift();Typr.U.P.lineTo(p,x,y);}}else if(v=="o6"||v=="o7"){var count=stack.length;var isX=v=="o6";for(var j=0;jMath.abs(c4y-y)){x=c4x+stack.shift();}else {y=c4y+stack.shift();}Typr.U.P.curveTo(p,c1x,c1y,c2x,c2y,jpx,jpy);Typr.U.P.curveTo(p,c3x,c3y,c4x,c4y,x,y);}}else if(v=="o14"){if(stack.length>0&&!haveWidth){width=stack.shift()+font.nominalWidthX;haveWidth=true;}if(stack.length==4){var adx=stack.shift();var ady=stack.shift();var bchar=stack.shift();var achar=stack.shift();var bind=Typr.CFF.glyphBySE(font,bchar);var aind=Typr.CFF.glyphBySE(font,achar);Typr.U._drawCFF(font.CharStrings[bind],state,font,p);state.x=adx;state.y=ady;Typr.U._drawCFF(font.CharStrings[aind],state,font,p);}if(open){Typr.U.P.closePath(p);open=false;}}else if(v=="o19"||v=="o20"){var hasWidthArg;hasWidthArg=stack.length%2!==0;if(hasWidthArg&&!haveWidth){width=stack.shift()+font.Private.nominalWidthX;}nStems+=stack.length>>1;stack.length=0;haveWidth=true;i+=nStems+7>>3;}else if(v=="o21"){if(stack.length>2&&!haveWidth){width=stack.shift()+font.Private.nominalWidthX;haveWidth=true;}y+=stack.pop();x+=stack.pop();if(open){ Typr.U.P.closePath(p); }Typr.U.P.moveTo(p,x,y);open=true;}else if(v=="o22"){if(stack.length>1&&!haveWidth){width=stack.shift()+font.Private.nominalWidthX;haveWidth=true;}x+=stack.pop();if(open){ Typr.U.P.closePath(p); }Typr.U.P.moveTo(p,x,y);open=true;}else if(v=="o25"){while(stack.length>6){x+=stack.shift();y+=stack.shift();Typr.U.P.lineTo(p,x,y);}c1x=x+stack.shift();c1y=y+stack.shift();c2x=c1x+stack.shift();c2y=c1y+stack.shift();x=c2x+stack.shift();y=c2y+stack.shift();Typr.U.P.curveTo(p,c1x,c1y,c2x,c2y,x,y);}else if(v=="o26"){if(stack.length%2){x+=stack.shift();}while(stack.length>0){c1x=x;c1y=y+stack.shift();c2x=c1x+stack.shift();c2y=c1y+stack.shift();x=c2x;y=c2y+stack.shift();Typr.U.P.curveTo(p,c1x,c1y,c2x,c2y,x,y);}}else if(v=="o27"){if(stack.length%2){y+=stack.shift();}while(stack.length>0){c1x=x+stack.shift();c1y=y;c2x=c1x+stack.shift();c2y=c1y+stack.shift();x=c2x+stack.shift();y=c2y;Typr.U.P.curveTo(p,c1x,c1y,c2x,c2y,x,y);}}else if(v=="o10"||v=="o29"){var obj=v=="o10"?font.Private:font;if(stack.length==0){console.log("error: empty stack");}else {var ind=stack.pop();var subr=obj.Subrs[ind+obj.Bias];state.x=x;state.y=y;state.nStems=nStems;state.haveWidth=haveWidth;state.width=width;state.open=open;Typr.U._drawCFF(subr,state,font,p);x=state.x;y=state.y;nStems=state.nStems;haveWidth=state.haveWidth;width=state.width;open=state.open;}}else if(v=="o30"||v=="o31"){var count,count1=stack.length;var index=0;var alternate=v=="o31";count=count1&~2;index+=count1-count;while(index -1 && glyphId < advanceWidthTable.length) { advanceWidth += advanceWidthTable[glyphId]; } return advanceWidth; }, 0); }); var width = Math.max.apply(Math, lineWidths); var lineHeight = (0 - descender) + ascender; var height = lineHeight * lines.length; // Multiply by fontScale to convert from font units to pixels return { width: width * fontScale, height: height * fontScale, lineHeight: lineHeight * fontScale, lineWidths: lineWidths.map(function (width) { return width * fontScale; }), descender: descender * fontScale, ascender: ascender * fontScale, }; }; ZdogFont.prototype.getTextPath = function getTextPath (text, fontSize, x, y, z, alignX, alignY) { var this$1 = this; if ( fontSize === void 0 ) fontSize=64; if ( x === void 0 ) x=0; if ( y === void 0 ) y=0; if ( z === void 0 ) z=0; if ( alignX === void 0 ) alignX='left'; if ( alignY === void 0 ) alignY='bottom'; if (!this._hasLoaded) { return []; } var lines = Array.isArray(text) ? text : text.split(TEXT_NEWLINE_REGEXP); var measurements = this.measureText(text, fontSize); var lineWidths = measurements.lineWidths; var lineHeight = measurements.lineHeight; return lines.map(function (line, lineIndex) { var ref = this$1.getTextOrigin(Object.assign({}, measurements, {width: lineWidths[lineIndex]}), x, y, z, alignX, alignY); var _x = ref[0]; var _y = ref[1]; var _z = ref[2]; y += lineHeight; var glyphs = typr_js.U.stringToGlyphs(this$1.font, line); var path = typr_js.U.glyphsToPath(this$1.font, glyphs); return this$1._convertPathCommands(path, fontSize, _x, _y, z); }).flat(); }; ZdogFont.prototype.getTextGlyphs = function getTextGlyphs (text, fontSize, x, y, z, alignX, alignY) { var this$1 = this; if ( fontSize === void 0 ) fontSize=64; if ( x === void 0 ) x=0; if ( y === void 0 ) y=0; if ( z === void 0 ) z=0; if ( alignX === void 0 ) alignX='left'; if ( alignY === void 0 ) alignY='bottom'; if (!this._hasLoaded) { return []; } var measurements = this.measureText(text, fontSize); var advanceWidthTable = this.font.hmtx.aWidth; var fontScale = this.getFontScale(fontSize); var lineWidths = measurements.lineWidths; var lineHeight = measurements.lineHeight; var lines = Array.isArray(text) ? text : text.split(TEXT_NEWLINE_REGEXP); return lines.map(function (line, lineIndex) { var glyphs = typr_js.U.stringToGlyphs(this$1.font, line); var ref = this$1.getTextOrigin(Object.assign({}, measurements, {width: lineWidths[lineIndex]}), x, y, z, alignX, alignY); var _x = ref[0]; var _y = ref[1]; var _z = ref[2]; y += lineHeight; return glyphs.filter(function (glyph) { return glyph !== -1; }).map(function (glyphId) { var path = typr_js.U.glyphToPath(this$1.font, glyphId); var shape = { translate: {x:_x, y: _y, z:_z}, path: this$1._convertPathCommands(path, fontSize, 0, 0, 0) }; _x += advanceWidthTable[glyphId] * fontScale; return shape; }); }).flat(); }; ZdogFont.prototype.getTextOrigin = function getTextOrigin (measuement, x, y, z, alignX, alignY) { if ( x === void 0 ) x=0; if ( y === void 0 ) y=0; if ( z === void 0 ) z=0; if ( alignX === void 0 ) alignX='left'; if ( alignY === void 0 ) alignY='bottom'; var width = measuement.width; var height = measuement.height; var lineHeight = measuement.lineHeight; switch (alignX) { case 'right': x -= width; break; case 'center': x -= width / 2; break; } switch (alignY) { case 'middle': y -= (height / 2)- lineHeight; break; case 'bottom': default: y -= height - lineHeight; break; } return [x, y, z]; }; // Convert Typr.js path commands to Zdog commands // Also apply font size scaling and coordinate adjustment // https://github.com/photopea/Typr.js // https://zzz.dog/shapes#shape-path-commands ZdogFont.prototype._convertPathCommands = function _convertPathCommands (path, fontSize, x, y, z) { if ( x === void 0 ) x=0; if ( y === void 0 ) y=0; if ( z === void 0 ) z=0; var yDir = -1; var xDir = 1; var fontScale = this.getFontScale(fontSize); var commands = path.cmds; // Apply font scale to all coords var coords = path.crds.map(function (coord) { return coord * fontScale; }); // Convert coords to Zdog commands var startCoord = null; var coordOffset = 0; return commands.map(function (cmd) { var result = null; if (!startCoord) { startCoord = {x: x + coords[coordOffset] * xDir, y: y + coords[coordOffset + 1] * yDir, z: z}; } switch (cmd) { case 'M': // moveTo command result = { move: {x: x + coords[coordOffset] * xDir, y: y + coords[coordOffset + 1] * yDir, z: z} }; coordOffset += 2; return result; case 'L': // lineTo command result = { line: {x: x + coords[coordOffset] * xDir, y: y + coords[coordOffset + 1] * yDir, z: z} }; coordOffset += 2; return result; case 'C': // curveTo command result = { bezier: [ {x: x + coords[coordOffset] * xDir, y: y + coords[coordOffset + 1] * yDir, z: z}, {x: x + coords[coordOffset + 2] * xDir, y: y + coords[coordOffset + 3] * yDir, z: z}, {x: x + coords[coordOffset + 4] * xDir, y: y + coords[coordOffset + 5] * yDir, z: z} ] }; coordOffset += 6; return result; case 'Q': // arcTo command result = { arc: [ {x: x + coords[coordOffset] * xDir, y: y + coords[coordOffset + 1] * yDir, z: z}, {x: x + coords[coordOffset + 2] * xDir, y: y + coords[coordOffset + 3] * yDir, z: z} ] }; coordOffset += 4; return result; case 'Z': // close path if (startCoord) { result = { line: startCoord }; startCoord = null; } return result; // unhandled type // currently, #rrggbb and X types (used in multicolor fonts) aren't supported default: return result; } }).filter(function (cmd) { return cmd !== null; }); // filter out null commands }; ZdogFont.prototype._fetchFontResource = function _fetchFontResource (source) { return new Promise(function (resolve, reject) { var request = new XMLHttpRequest(); // Fetch as an arrayBuffer for Typr.parse request.responseType = 'arraybuffer'; request.open('GET', source, true); request.onreadystatechange = function (e) { if (request.readyState === 4) { if (request.status >= 200 && request.status < 300) { resolve(request.response); } else { reject(("HTTP error " + (request.status) + ": " + (request.statusText))); } } }; request.send(null); }); }; Zdog.Font = ZdogFont; return Zdog; } function objectWithoutProperties (obj, exclude) { var target = {}; for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k) && exclude.indexOf(k) === -1) target[k] = obj[k]; return target; } function registerTextClass(Zdog) { // Zdog.Text class var ZdogText = /*@__PURE__*/(function (superclass) { function ZdogText(props) { // Set missing props to default values props = Zdog.extend({ font: null, value: '', fontSize: 64, textAlign: 'left', textBaseline: 'bottom', }, props); // Split props var font = props.font; var value = props.value; var fontSize = props.fontSize; var textAlign = props.textAlign; var textBaseline = props.textBaseline; var rest = objectWithoutProperties( props, ["font", "value", "fontSize", "textAlign", "textBaseline"] ); var shapeProps = rest; // Create shape object superclass.call(this, Object.assign({}, shapeProps, {closed: true, visible: false, // hide until font is loaded path: [{}]})); this._font = null; this._value = value; this._fontSize = fontSize; this._textAlign = textAlign; this._textBaseline = textBaseline; this.font = font; } if ( superclass ) ZdogText.__proto__ = superclass; ZdogText.prototype = Object.create( superclass && superclass.prototype ); ZdogText.prototype.constructor = ZdogText; var prototypeAccessors = { font: { configurable: true },value: { configurable: true },fontSize: { configurable: true },textAlign: { configurable: true },textBaseline: { configurable: true } }; ZdogText.prototype.updateText = function updateText () { var path = this.font.getTextPath(this.value, this.fontSize, 0, 0, 0, this.textAlign, this.textBaseline); if (path.length == 0) { // zdog doesn't know what to do with empty path arrays this.path = [{}]; this.visible = false; } else { this.path = path; this.visible = true; } this.updatePath(); }; prototypeAccessors.font.set = function (newFont) { var this$1 = this; this._font = newFont; this.font.waitForLoad().then(function () { this$1.updateText(); this$1.visible = true; // Find root Zdog.Illustration instance var root = this$1.addTo; while (root.addTo !== undefined) { root = root.addTo; } // Update render graph if (root && typeof root.updateRenderGraph === 'function') { root.updateRenderGraph(); } }); }; prototypeAccessors.font.get = function () { return this._font; }; prototypeAccessors.value.set = function (newValue) { this._value = newValue; this.updateText(); }; prototypeAccessors.value.get = function () { return this._value; }; prototypeAccessors.fontSize.set = function (newSize) { this._fontSize = newSize; this.updateText(); }; prototypeAccessors.fontSize.get = function () { return this._fontSize; }; prototypeAccessors.textAlign.set = function (newValue) { this._textAlign = newValue; this.updateText(); }; prototypeAccessors.textAlign.get = function () { return this._textAlign; }; prototypeAccessors.textBaseline.set = function (newValue) { this._textBaseline = newValue; this.updateText(); }; prototypeAccessors.textBaseline.get = function () { return this._textBaseline; }; Object.defineProperties( ZdogText.prototype, prototypeAccessors ); return ZdogText; }(Zdog.Shape)); ZdogText.optionKeys = ZdogText.optionKeys.concat(['font', 'fontSize', 'value', 'textAlign', 'textBaseline']); Zdog.Text = ZdogText; return Zdog; } function objectWithoutProperties$1 (obj, exclude) { var target = {}; for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k) && exclude.indexOf(k) === -1) target[k] = obj[k]; return target; } function registerTextGroupClass(Zdog) { // Zdog.TextGroup class var ZdogTextGroup = /*@__PURE__*/(function (superclass) { function ZdogTextGroup(props) { // Set missing props to default values props = Zdog.extend({ font: null, value: '', fontSize: 64, textAlign: 'left', textBaseline: 'bottom', color: '#333', fill: false, stroke: 1, }, props); // Split props var font = props.font; var value = props.value; var fontSize = props.fontSize; var textAlign = props.textAlign; var textBaseline = props.textBaseline; var color = props.color; var fill = props.fill; var stroke = props.stroke; var rest = objectWithoutProperties$1( props, ["font", "value", "fontSize", "textAlign", "textBaseline", "color", "fill", "stroke"] ); var groupProps = rest; // Create group object superclass.call(this, Object.assign({}, groupProps, {visible: false})); this._font = null; this._value = value; this._fontSize = fontSize; this._textAlign = textAlign; this._textBaseline = textBaseline; this._color = color; this._fill = fill; this._stroke = stroke; this.font = font; } if ( superclass ) ZdogTextGroup.__proto__ = superclass; ZdogTextGroup.prototype = Object.create( superclass && superclass.prototype ); ZdogTextGroup.prototype.constructor = ZdogTextGroup; var prototypeAccessors = { font: { configurable: true },value: { configurable: true },fontSize: { configurable: true },textAlign: { configurable: true },textBaseline: { configurable: true },color: { configurable: true },fill: { configurable: true },stroke: { configurable: true } }; ZdogTextGroup.prototype.updateText = function updateText () { var this$1 = this; // Remove old children while (this.children.length > 0) { this.removeChild(this.children[0]); } // Get text paths for each glyph var glyphs = this.font.getTextGlyphs(this.value, this.fontSize, 0, 0, 0, this.textAlign, this.textBaseline); // Convert glyphs to new shapes glyphs.filter(function (shape) { return shape.path.length > 0; }).forEach(function (shape) { this$1.addChild(new Zdog.Shape({ translate: shape.translate, path: shape.path, color: this$1.color, fill: this$1.fill, stroke: this$1.stroke, closed: true, })); }); this.updateFlatGraph(); }; prototypeAccessors.font.set = function (newFont) { var this$1 = this; this._font = newFont; this._font.waitForLoad().then(function () { this$1.updateText(); this$1.visible = true; // Find root Zdog.Illustration instance var root = this$1.addTo; while (root.addTo !== undefined) { root = root.addTo; } // Update render graph if (root && typeof root.updateRenderGraph === 'function') { root.updateRenderGraph(); } }); }; prototypeAccessors.font.get = function () { return this._font; }; prototypeAccessors.value.set = function (newValue) { this._value = newValue; this.updateText(); }; prototypeAccessors.value.get = function () { return this._value; }; prototypeAccessors.fontSize.set = function (newSize) { this._fontSize = newSize; this.updateText(); }; prototypeAccessors.fontSize.get = function () { return this._fontSize; }; prototypeAccessors.textAlign.set = function (newValue) { this._textAlign = newValue; this.updateText(); }; prototypeAccessors.textAlign.get = function () { return this._textAlign; }; prototypeAccessors.textBaseline.set = function (newValue) { this._textBaseline = newValue; this.updateText(); }; prototypeAccessors.textBaseline.get = function () { return this._textBaseline; }; prototypeAccessors.color.set = function (newColor) { this._color = newColor; this.children.forEach(function (child) { return child.color = newColor; }); }; prototypeAccessors.color.get = function () { return this._color; }; prototypeAccessors.fill.set = function (newFill) { this._fill = newFill; this.children.forEach(function (child) { return child.fill = newFill; }); }; prototypeAccessors.fill.get = function () { return this._fill; }; prototypeAccessors.stroke.set = function (newStroke) { this._stroke = newStroke; this.children.forEach(function (child) { return child.stroke = newStroke; }); }; prototypeAccessors.stroke.get = function () { return this._stroke; }; Object.defineProperties( ZdogTextGroup.prototype, prototypeAccessors ); return ZdogTextGroup; }(Zdog.Group)); ZdogTextGroup.optionKeys = ZdogTextGroup.optionKeys.concat(['color', 'fill', 'stroke', 'font', 'fontSize', 'value', 'textAlign', 'textBaseline']); Zdog.TextGroup = ZdogTextGroup; return Zdog; } var index = { init: function init(Zdog) { // Global font list to keep track of all fonts Zdog.FontList = []; // Helper to wait for all fonts to load Zdog.waitForFonts = function() { return Promise.all(Zdog.FontList.map(function (font) { return font.waitForLoad(); })); }; // Register Zfont classes onto the Zdog object registerFontClass(Zdog); registerTextClass(Zdog); registerTextGroupClass(Zdog); return Zdog; }, version: "1.2.8", }; export default index; //# sourceMappingURL=zfont.es.js.map ================================================ FILE: dist/zfont.js ================================================ /*! * Zfont v1.2.8 * Text plugin for Zdog * 2019 James Daniel * MIT Licensed * github.com/jaames/zfont */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = global || self, global.Zfont = factory()); }(this, (function () { 'use strict'; var Typr={};Typr.parse=function(buff){var bin=Typr._bin;var data=new Uint8Array(buff);var offset=0;var sfnt_version=bin.readFixed(data,offset);offset+=4;var numTables=bin.readUshort(data,offset);offset+=2;var searchRange=bin.readUshort(data,offset);offset+=2;var entrySelector=bin.readUshort(data,offset);offset+=2;var rangeShift=bin.readUshort(data,offset);offset+=2;var tags=["cmap","head","hhea","maxp","hmtx","name","OS/2","post","loca","glyf","kern","CFF ","GPOS","GSUB","SVG "];var obj={_data:data};var tabs={};for(var i=0;i>>i&1)!=0){ num++; } }return num};Typr._lctf.readClassDef=function(data,offset){var bin=Typr._bin;var obj=[];var format=bin.readUshort(data,offset);offset+=2;if(format==1){var startGlyph=bin.readUshort(data,offset);offset+=2;var glyphCount=bin.readUshort(data,offset);offset+=2;for(var i=0;i255){ return -1; }return Typr.CFF.glyphByUnicode(cff,Typr.CFF.tableSE[charcode])};Typr.CFF.readEncoding=function(data,offset,num){var bin=Typr._bin;var array=[".notdef"];var format=data[offset];offset++;if(format==0){var nCodes=data[offset];offset++;for(var i=0;i>4,nib1=b&15;if(nib0!=15){ nibs.push(nib0); }if(nib1!=15){ nibs.push(nib1); }if(nib1==15){ break }}var s="";var chars=[0,1,2,3,4,5,6,7,8,9,".","e","e-","reserved","-","endOfNumber"];for(var i=0;i=gl.xMax||gl.yMin>=gl.yMax){ return null; }if(gl.noc>0){gl.endPts=[];for(var i=0;i>>8;format&=15;if(format==0){ offset=Typr.kern.readFormat0(data,offset,map); }else { throw "unknown kern table format: "+format }}return map};Typr.kern.parseV1=function(data,offset,length,font){var bin=Typr._bin;var version=bin.readFixed(data,offset);offset+=4;var nTables=bin.readUint(data,offset);offset+=4;var map={glyph1:[],rval:[]};for(var i=0;i>>8;format&=15;if(format==0){ offset=Typr.kern.readFormat0(data,offset,map); }else { throw "unknown kern table format: "+format }}return map};Typr.kern.readFormat0=function(data,offset,map){var bin=Typr._bin;var pleft=-1;var nPairs=bin.readUshort(data,offset);offset+=2;var searchRange=bin.readUshort(data,offset);offset+=2;var entrySelector=bin.readUshort(data,offset);offset+=2;var rangeShift=bin.readUshort(data,offset);offset+=2;for(var j=0;j=tab.map.length){ return 0; }return tab.map[code]}else if(tab.format==4){var sind=-1;for(var i=0;icode){ return 0; }var gli=0;if(tab.idRangeOffset[sind]!=0){ gli=tab.glyphIdArray[code-tab.startCount[sind]+(tab.idRangeOffset[sind]>>1)-(tab.idRangeOffset.length-sind)]; }else { gli=code+tab.idDelta[sind]; }return gli&65535}else if(tab.format==12){if(code>tab.groups[tab.groups.length-1][1]){ return 0; }for(var i=0;i-1){ Typr.U._simpleGlyph(gl,path); }else { Typr.U._compoGlyph(gl,font,path); }}};Typr.U._simpleGlyph=function(gl,p){for(var c=0;c65535){ i++; }gls.push(Typr.U.codeToGlyph(font,cc));}var gsub=font["GSUB"];if(gsub==null){ return gls; }var llist=gsub.lookupList,flist=gsub.featureList;var wsep='\n\t" ,.:;!?() ،';var R="آأؤإاةدذرزوٱٲٳٵٶٷڈډڊڋڌڍڎڏڐڑڒړڔڕږڗژڙۀۃۄۅۆۇۈۉۊۋۍۏےۓەۮۯܐܕܖܗܘܙܞܨܪܬܯݍݙݚݛݫݬݱݳݴݸݹࡀࡆࡇࡉࡔࡧࡩࡪࢪࢫࢬࢮࢱࢲࢹૅેૉ૊૎૏ૐ૑૒૝ૡ૤૯஁ஃ஄அஉ஌எஏ஑னப஫஬";var L="ꡲ્૗";for(var ci=0;cirlim){ continue; }var good=true;for(var l=0;lrlim){ continue; }var good=true;for(var l=0;l>1;stack.length=0;haveWidth=true;}else if(v=="o3"||v=="o23"){var hasWidthArg;hasWidthArg=stack.length%2!==0;if(hasWidthArg&&!haveWidth){width=stack.shift()+font.Private.nominalWidthX;}nStems+=stack.length>>1;stack.length=0;haveWidth=true;}else if(v=="o4"){if(stack.length>1&&!haveWidth){width=stack.shift()+font.Private.nominalWidthX;haveWidth=true;}if(open){ Typr.U.P.closePath(p); }y+=stack.pop();Typr.U.P.moveTo(p,x,y);open=true;}else if(v=="o5"){while(stack.length>0){x+=stack.shift();y+=stack.shift();Typr.U.P.lineTo(p,x,y);}}else if(v=="o6"||v=="o7"){var count=stack.length;var isX=v=="o6";for(var j=0;jMath.abs(c4y-y)){x=c4x+stack.shift();}else {y=c4y+stack.shift();}Typr.U.P.curveTo(p,c1x,c1y,c2x,c2y,jpx,jpy);Typr.U.P.curveTo(p,c3x,c3y,c4x,c4y,x,y);}}else if(v=="o14"){if(stack.length>0&&!haveWidth){width=stack.shift()+font.nominalWidthX;haveWidth=true;}if(stack.length==4){var adx=stack.shift();var ady=stack.shift();var bchar=stack.shift();var achar=stack.shift();var bind=Typr.CFF.glyphBySE(font,bchar);var aind=Typr.CFF.glyphBySE(font,achar);Typr.U._drawCFF(font.CharStrings[bind],state,font,p);state.x=adx;state.y=ady;Typr.U._drawCFF(font.CharStrings[aind],state,font,p);}if(open){Typr.U.P.closePath(p);open=false;}}else if(v=="o19"||v=="o20"){var hasWidthArg;hasWidthArg=stack.length%2!==0;if(hasWidthArg&&!haveWidth){width=stack.shift()+font.Private.nominalWidthX;}nStems+=stack.length>>1;stack.length=0;haveWidth=true;i+=nStems+7>>3;}else if(v=="o21"){if(stack.length>2&&!haveWidth){width=stack.shift()+font.Private.nominalWidthX;haveWidth=true;}y+=stack.pop();x+=stack.pop();if(open){ Typr.U.P.closePath(p); }Typr.U.P.moveTo(p,x,y);open=true;}else if(v=="o22"){if(stack.length>1&&!haveWidth){width=stack.shift()+font.Private.nominalWidthX;haveWidth=true;}x+=stack.pop();if(open){ Typr.U.P.closePath(p); }Typr.U.P.moveTo(p,x,y);open=true;}else if(v=="o25"){while(stack.length>6){x+=stack.shift();y+=stack.shift();Typr.U.P.lineTo(p,x,y);}c1x=x+stack.shift();c1y=y+stack.shift();c2x=c1x+stack.shift();c2y=c1y+stack.shift();x=c2x+stack.shift();y=c2y+stack.shift();Typr.U.P.curveTo(p,c1x,c1y,c2x,c2y,x,y);}else if(v=="o26"){if(stack.length%2){x+=stack.shift();}while(stack.length>0){c1x=x;c1y=y+stack.shift();c2x=c1x+stack.shift();c2y=c1y+stack.shift();x=c2x;y=c2y+stack.shift();Typr.U.P.curveTo(p,c1x,c1y,c2x,c2y,x,y);}}else if(v=="o27"){if(stack.length%2){y+=stack.shift();}while(stack.length>0){c1x=x+stack.shift();c1y=y;c2x=c1x+stack.shift();c2y=c1y+stack.shift();x=c2x+stack.shift();y=c2y;Typr.U.P.curveTo(p,c1x,c1y,c2x,c2y,x,y);}}else if(v=="o10"||v=="o29"){var obj=v=="o10"?font.Private:font;if(stack.length==0){console.log("error: empty stack");}else {var ind=stack.pop();var subr=obj.Subrs[ind+obj.Bias];state.x=x;state.y=y;state.nStems=nStems;state.haveWidth=haveWidth;state.width=width;state.open=open;Typr.U._drawCFF(subr,state,font,p);x=state.x;y=state.y;nStems=state.nStems;haveWidth=state.haveWidth;width=state.width;open=state.open;}}else if(v=="o30"||v=="o31"){var count,count1=stack.length;var index=0;var alternate=v=="o31";count=count1&~2;index+=count1-count;while(index -1 && glyphId < advanceWidthTable.length) { advanceWidth += advanceWidthTable[glyphId]; } return advanceWidth; }, 0); }); var width = Math.max.apply(Math, lineWidths); var lineHeight = (0 - descender) + ascender; var height = lineHeight * lines.length; // Multiply by fontScale to convert from font units to pixels return { width: width * fontScale, height: height * fontScale, lineHeight: lineHeight * fontScale, lineWidths: lineWidths.map(function (width) { return width * fontScale; }), descender: descender * fontScale, ascender: ascender * fontScale, }; }; ZdogFont.prototype.getTextPath = function getTextPath (text, fontSize, x, y, z, alignX, alignY) { var this$1 = this; if ( fontSize === void 0 ) fontSize=64; if ( x === void 0 ) x=0; if ( y === void 0 ) y=0; if ( z === void 0 ) z=0; if ( alignX === void 0 ) alignX='left'; if ( alignY === void 0 ) alignY='bottom'; if (!this._hasLoaded) { return []; } var lines = Array.isArray(text) ? text : text.split(TEXT_NEWLINE_REGEXP); var measurements = this.measureText(text, fontSize); var lineWidths = measurements.lineWidths; var lineHeight = measurements.lineHeight; return lines.map(function (line, lineIndex) { var ref = this$1.getTextOrigin(Object.assign({}, measurements, {width: lineWidths[lineIndex]}), x, y, z, alignX, alignY); var _x = ref[0]; var _y = ref[1]; var _z = ref[2]; y += lineHeight; var glyphs = typr_js.U.stringToGlyphs(this$1.font, line); var path = typr_js.U.glyphsToPath(this$1.font, glyphs); return this$1._convertPathCommands(path, fontSize, _x, _y, z); }).flat(); }; ZdogFont.prototype.getTextGlyphs = function getTextGlyphs (text, fontSize, x, y, z, alignX, alignY) { var this$1 = this; if ( fontSize === void 0 ) fontSize=64; if ( x === void 0 ) x=0; if ( y === void 0 ) y=0; if ( z === void 0 ) z=0; if ( alignX === void 0 ) alignX='left'; if ( alignY === void 0 ) alignY='bottom'; if (!this._hasLoaded) { return []; } var measurements = this.measureText(text, fontSize); var advanceWidthTable = this.font.hmtx.aWidth; var fontScale = this.getFontScale(fontSize); var lineWidths = measurements.lineWidths; var lineHeight = measurements.lineHeight; var lines = Array.isArray(text) ? text : text.split(TEXT_NEWLINE_REGEXP); return lines.map(function (line, lineIndex) { var glyphs = typr_js.U.stringToGlyphs(this$1.font, line); var ref = this$1.getTextOrigin(Object.assign({}, measurements, {width: lineWidths[lineIndex]}), x, y, z, alignX, alignY); var _x = ref[0]; var _y = ref[1]; var _z = ref[2]; y += lineHeight; return glyphs.filter(function (glyph) { return glyph !== -1; }).map(function (glyphId) { var path = typr_js.U.glyphToPath(this$1.font, glyphId); var shape = { translate: {x:_x, y: _y, z:_z}, path: this$1._convertPathCommands(path, fontSize, 0, 0, 0) }; _x += advanceWidthTable[glyphId] * fontScale; return shape; }); }).flat(); }; ZdogFont.prototype.getTextOrigin = function getTextOrigin (measuement, x, y, z, alignX, alignY) { if ( x === void 0 ) x=0; if ( y === void 0 ) y=0; if ( z === void 0 ) z=0; if ( alignX === void 0 ) alignX='left'; if ( alignY === void 0 ) alignY='bottom'; var width = measuement.width; var height = measuement.height; var lineHeight = measuement.lineHeight; switch (alignX) { case 'right': x -= width; break; case 'center': x -= width / 2; break; } switch (alignY) { case 'middle': y -= (height / 2)- lineHeight; break; case 'bottom': default: y -= height - lineHeight; break; } return [x, y, z]; }; // Convert Typr.js path commands to Zdog commands // Also apply font size scaling and coordinate adjustment // https://github.com/photopea/Typr.js // https://zzz.dog/shapes#shape-path-commands ZdogFont.prototype._convertPathCommands = function _convertPathCommands (path, fontSize, x, y, z) { if ( x === void 0 ) x=0; if ( y === void 0 ) y=0; if ( z === void 0 ) z=0; var yDir = -1; var xDir = 1; var fontScale = this.getFontScale(fontSize); var commands = path.cmds; // Apply font scale to all coords var coords = path.crds.map(function (coord) { return coord * fontScale; }); // Convert coords to Zdog commands var startCoord = null; var coordOffset = 0; return commands.map(function (cmd) { var result = null; if (!startCoord) { startCoord = {x: x + coords[coordOffset] * xDir, y: y + coords[coordOffset + 1] * yDir, z: z}; } switch (cmd) { case 'M': // moveTo command result = { move: {x: x + coords[coordOffset] * xDir, y: y + coords[coordOffset + 1] * yDir, z: z} }; coordOffset += 2; return result; case 'L': // lineTo command result = { line: {x: x + coords[coordOffset] * xDir, y: y + coords[coordOffset + 1] * yDir, z: z} }; coordOffset += 2; return result; case 'C': // curveTo command result = { bezier: [ {x: x + coords[coordOffset] * xDir, y: y + coords[coordOffset + 1] * yDir, z: z}, {x: x + coords[coordOffset + 2] * xDir, y: y + coords[coordOffset + 3] * yDir, z: z}, {x: x + coords[coordOffset + 4] * xDir, y: y + coords[coordOffset + 5] * yDir, z: z} ] }; coordOffset += 6; return result; case 'Q': // arcTo command result = { arc: [ {x: x + coords[coordOffset] * xDir, y: y + coords[coordOffset + 1] * yDir, z: z}, {x: x + coords[coordOffset + 2] * xDir, y: y + coords[coordOffset + 3] * yDir, z: z} ] }; coordOffset += 4; return result; case 'Z': // close path if (startCoord) { result = { line: startCoord }; startCoord = null; } return result; // unhandled type // currently, #rrggbb and X types (used in multicolor fonts) aren't supported default: return result; } }).filter(function (cmd) { return cmd !== null; }); // filter out null commands }; ZdogFont.prototype._fetchFontResource = function _fetchFontResource (source) { return new Promise(function (resolve, reject) { var request = new XMLHttpRequest(); // Fetch as an arrayBuffer for Typr.parse request.responseType = 'arraybuffer'; request.open('GET', source, true); request.onreadystatechange = function (e) { if (request.readyState === 4) { if (request.status >= 200 && request.status < 300) { resolve(request.response); } else { reject(("HTTP error " + (request.status) + ": " + (request.statusText))); } } }; request.send(null); }); }; Zdog.Font = ZdogFont; return Zdog; } function objectWithoutProperties (obj, exclude) { var target = {}; for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k) && exclude.indexOf(k) === -1) target[k] = obj[k]; return target; } function registerTextClass(Zdog) { // Zdog.Text class var ZdogText = /*@__PURE__*/(function (superclass) { function ZdogText(props) { // Set missing props to default values props = Zdog.extend({ font: null, value: '', fontSize: 64, textAlign: 'left', textBaseline: 'bottom', }, props); // Split props var font = props.font; var value = props.value; var fontSize = props.fontSize; var textAlign = props.textAlign; var textBaseline = props.textBaseline; var rest = objectWithoutProperties( props, ["font", "value", "fontSize", "textAlign", "textBaseline"] ); var shapeProps = rest; // Create shape object superclass.call(this, Object.assign({}, shapeProps, {closed: true, visible: false, // hide until font is loaded path: [{}]})); this._font = null; this._value = value; this._fontSize = fontSize; this._textAlign = textAlign; this._textBaseline = textBaseline; this.font = font; } if ( superclass ) ZdogText.__proto__ = superclass; ZdogText.prototype = Object.create( superclass && superclass.prototype ); ZdogText.prototype.constructor = ZdogText; var prototypeAccessors = { font: { configurable: true },value: { configurable: true },fontSize: { configurable: true },textAlign: { configurable: true },textBaseline: { configurable: true } }; ZdogText.prototype.updateText = function updateText () { var path = this.font.getTextPath(this.value, this.fontSize, 0, 0, 0, this.textAlign, this.textBaseline); if (path.length == 0) { // zdog doesn't know what to do with empty path arrays this.path = [{}]; this.visible = false; } else { this.path = path; this.visible = true; } this.updatePath(); }; prototypeAccessors.font.set = function (newFont) { var this$1 = this; this._font = newFont; this.font.waitForLoad().then(function () { this$1.updateText(); this$1.visible = true; // Find root Zdog.Illustration instance var root = this$1.addTo; while (root.addTo !== undefined) { root = root.addTo; } // Update render graph if (root && typeof root.updateRenderGraph === 'function') { root.updateRenderGraph(); } }); }; prototypeAccessors.font.get = function () { return this._font; }; prototypeAccessors.value.set = function (newValue) { this._value = newValue; this.updateText(); }; prototypeAccessors.value.get = function () { return this._value; }; prototypeAccessors.fontSize.set = function (newSize) { this._fontSize = newSize; this.updateText(); }; prototypeAccessors.fontSize.get = function () { return this._fontSize; }; prototypeAccessors.textAlign.set = function (newValue) { this._textAlign = newValue; this.updateText(); }; prototypeAccessors.textAlign.get = function () { return this._textAlign; }; prototypeAccessors.textBaseline.set = function (newValue) { this._textBaseline = newValue; this.updateText(); }; prototypeAccessors.textBaseline.get = function () { return this._textBaseline; }; Object.defineProperties( ZdogText.prototype, prototypeAccessors ); return ZdogText; }(Zdog.Shape)); ZdogText.optionKeys = ZdogText.optionKeys.concat(['font', 'fontSize', 'value', 'textAlign', 'textBaseline']); Zdog.Text = ZdogText; return Zdog; } function objectWithoutProperties$1 (obj, exclude) { var target = {}; for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k) && exclude.indexOf(k) === -1) target[k] = obj[k]; return target; } function registerTextGroupClass(Zdog) { // Zdog.TextGroup class var ZdogTextGroup = /*@__PURE__*/(function (superclass) { function ZdogTextGroup(props) { // Set missing props to default values props = Zdog.extend({ font: null, value: '', fontSize: 64, textAlign: 'left', textBaseline: 'bottom', color: '#333', fill: false, stroke: 1, }, props); // Split props var font = props.font; var value = props.value; var fontSize = props.fontSize; var textAlign = props.textAlign; var textBaseline = props.textBaseline; var color = props.color; var fill = props.fill; var stroke = props.stroke; var rest = objectWithoutProperties$1( props, ["font", "value", "fontSize", "textAlign", "textBaseline", "color", "fill", "stroke"] ); var groupProps = rest; // Create group object superclass.call(this, Object.assign({}, groupProps, {visible: false})); this._font = null; this._value = value; this._fontSize = fontSize; this._textAlign = textAlign; this._textBaseline = textBaseline; this._color = color; this._fill = fill; this._stroke = stroke; this.font = font; } if ( superclass ) ZdogTextGroup.__proto__ = superclass; ZdogTextGroup.prototype = Object.create( superclass && superclass.prototype ); ZdogTextGroup.prototype.constructor = ZdogTextGroup; var prototypeAccessors = { font: { configurable: true },value: { configurable: true },fontSize: { configurable: true },textAlign: { configurable: true },textBaseline: { configurable: true },color: { configurable: true },fill: { configurable: true },stroke: { configurable: true } }; ZdogTextGroup.prototype.updateText = function updateText () { var this$1 = this; // Remove old children while (this.children.length > 0) { this.removeChild(this.children[0]); } // Get text paths for each glyph var glyphs = this.font.getTextGlyphs(this.value, this.fontSize, 0, 0, 0, this.textAlign, this.textBaseline); // Convert glyphs to new shapes glyphs.filter(function (shape) { return shape.path.length > 0; }).forEach(function (shape) { this$1.addChild(new Zdog.Shape({ translate: shape.translate, path: shape.path, color: this$1.color, fill: this$1.fill, stroke: this$1.stroke, closed: true, })); }); this.updateFlatGraph(); }; prototypeAccessors.font.set = function (newFont) { var this$1 = this; this._font = newFont; this._font.waitForLoad().then(function () { this$1.updateText(); this$1.visible = true; // Find root Zdog.Illustration instance var root = this$1.addTo; while (root.addTo !== undefined) { root = root.addTo; } // Update render graph if (root && typeof root.updateRenderGraph === 'function') { root.updateRenderGraph(); } }); }; prototypeAccessors.font.get = function () { return this._font; }; prototypeAccessors.value.set = function (newValue) { this._value = newValue; this.updateText(); }; prototypeAccessors.value.get = function () { return this._value; }; prototypeAccessors.fontSize.set = function (newSize) { this._fontSize = newSize; this.updateText(); }; prototypeAccessors.fontSize.get = function () { return this._fontSize; }; prototypeAccessors.textAlign.set = function (newValue) { this._textAlign = newValue; this.updateText(); }; prototypeAccessors.textAlign.get = function () { return this._textAlign; }; prototypeAccessors.textBaseline.set = function (newValue) { this._textBaseline = newValue; this.updateText(); }; prototypeAccessors.textBaseline.get = function () { return this._textBaseline; }; prototypeAccessors.color.set = function (newColor) { this._color = newColor; this.children.forEach(function (child) { return child.color = newColor; }); }; prototypeAccessors.color.get = function () { return this._color; }; prototypeAccessors.fill.set = function (newFill) { this._fill = newFill; this.children.forEach(function (child) { return child.fill = newFill; }); }; prototypeAccessors.fill.get = function () { return this._fill; }; prototypeAccessors.stroke.set = function (newStroke) { this._stroke = newStroke; this.children.forEach(function (child) { return child.stroke = newStroke; }); }; prototypeAccessors.stroke.get = function () { return this._stroke; }; Object.defineProperties( ZdogTextGroup.prototype, prototypeAccessors ); return ZdogTextGroup; }(Zdog.Group)); ZdogTextGroup.optionKeys = ZdogTextGroup.optionKeys.concat(['color', 'fill', 'stroke', 'font', 'fontSize', 'value', 'textAlign', 'textBaseline']); Zdog.TextGroup = ZdogTextGroup; return Zdog; } var index = { init: function init(Zdog) { // Global font list to keep track of all fonts Zdog.FontList = []; // Helper to wait for all fonts to load Zdog.waitForFonts = function() { return Promise.all(Zdog.FontList.map(function (font) { return font.waitForLoad(); })); }; // Register Zfont classes onto the Zdog object registerFontClass(Zdog); registerTextClass(Zdog); registerTextGroupClass(Zdog); return Zdog; }, version: "1.2.8", }; return index; }))); //# sourceMappingURL=zfont.js.map ================================================ FILE: package.json ================================================ { "name": "zfont", "version": "1.2.8", "description": "Text plugin for Zdog", "module": "dist/zfont.es.js", "main": "dist/zfont.js", "files": [ "dist/zfont.min.js", "dist/zfont.js", "dist/zfont.es.js" ], "scripts": { "start": "rollup -c --watch --environment DEV_SERVER,BUILD:development", "dev": "rollup -c --environment BUILD:development", "build": "npm run dev && npm run build:es && npm run build:min", "build:min": "rollup -c --environment BUILD:production", "build:es": "rollup -c --environment ES_MODULE,BUILD:production" }, "dependencies": { "typr.js": "^1.0.0" }, "devDependencies": { "rollup": "^1.13.0", "rollup-plugin-alias": "^1.5.1", "rollup-plugin-buble": "^0.19.6", "rollup-plugin-commonjs": "^10.0.0", "rollup-plugin-filesize": "^6.1.0", "rollup-plugin-livereload": "^1.0.4", "rollup-plugin-node-resolve": "^5.0.1", "rollup-plugin-replace": "^2.2.0", "rollup-plugin-serve": "^1.0.1", "rollup-plugin-uglify": "^6.0.2" }, "keywords": [ "zdog", "3d", "font", "text", "truetype", "ttf" ], "repository": { "type": "git", "url": "git+https://github.com/jaames/zfont.git" }, "author": "James Daniel ", "license": "MIT", "bugs": { "url": "https://github.com/jaames/zfont/issues" }, "homepage": "https://github.com/jaames/zfont" } ================================================ FILE: rollup.config.js ================================================ import { version, description } from './package.json'; import filesize from 'rollup-plugin-filesize'; import buble from 'rollup-plugin-buble'; import alias from 'rollup-plugin-alias'; import commonjs from 'rollup-plugin-commonjs'; import nodeResolve from 'rollup-plugin-node-resolve'; import replace from 'rollup-plugin-replace'; import { uglify } from 'rollup-plugin-uglify'; import serve from 'rollup-plugin-serve'; import livereload from 'rollup-plugin-livereload'; const build = process.env.BUILD || "development"; const devserver = process.env.DEV_SERVER || false; const esmodule = process.env.ES_MODULE || false; const prod = build === "production"; const banner = `/*! * Zfont v${version} * ${description} * 2019 James Daniel * MIT Licensed * github.com/jaames/zfont */ ` module.exports = { input: 'src/index.js', output: [ esmodule ? { file: 'dist/zfont.es.js', format: 'es', name: 'Zfont', banner: banner, sourcemap: true, sourcemapFile: 'dist/zfont.es.map' } : { file: prod ? 'dist/zfont.min.js' : 'dist/zfont.js', format: 'umd', name: 'Zfont', banner: banner, sourcemap: true, sourcemapFile: prod ? 'dist/zfont.min.js.map' : 'dist/zfont.js.map' } ].filter(Boolean), plugins: [ alias({ resolve: ['.jsx', '.js'] }), replace({ VERSION: JSON.stringify(version), PROD: prod ? 'true' : 'false', DEV_SERVER: devserver ? 'true' : 'false' }), buble({ jsx: 'h', objectAssign: 'Object.assign', transforms: { } }), nodeResolve(), commonjs(), !prod && devserver ? serve({ contentBase: ['dist', 'demo'] }) : false, !prod && devserver ? livereload() : false, // show filesize stats when building dist files !devserver ? filesize() : false, // only minify if we're producing a non-es production build prod && !esmodule ? uglify({ output: { comments: function(node, comment) { if (comment.type === 'comment2') { // preserve banner comment return /\!/i.test(comment.value); } return false; } } }) : false, ].filter(Boolean) }; ================================================ FILE: src/ZdogFont.js ================================================ import Typr from 'typr.js'; const TEXT_NEWLINE_REGEXP = /\r?\n/; export function registerFontClass(Zdog) { // Zdog.Font class class ZdogFont { constructor(props) { // Set missing props to default values props = Zdog.extend({ src: '', }, props); this.src = props.src; this.font = null; this._hasLoaded = false; this._loadCallbacks = []; // Add this font instance to the internal font list Zdog.FontList.push(this); // Begin loading font file this._fetchFontResource(this.src) .then(buffer => { const font = Typr.parse(buffer); // check font fields to see if the font was parsed correctly if ((!font.head) || (!font.hmtx) || (!font.hhea) || (!font.glyf)) { // get a list of missing font fields (only checks for ones that zfont uses) const missingFields = ['head', 'hmtx', 'hhea', 'glyf'].filter(field => !font[field]); throw new Error(`Typr.js could not parse this font (unable to find ${ missingFields.join(', ') })`); } return font; }) .then(font => { this.font = font; this._hasLoaded = true; this._loadCallbacks.forEach(callback => callback()); }) .catch(err => { throw new Error(`Unable to load font from ${this.src}:\n${err}`); }) } waitForLoad() { return new Promise((resolve, reject) => { // If the font is loaded, we can resolve right away if (this._hasLoaded && this._hasLoaded) { resolve(); } // Otherwise, wait for it to load else { this._loadCallbacks.push(resolve); } }); } getFontScale(fontSize) { if (!this._hasLoaded) { return null; } else { return 1 / this.font.head.unitsPerEm * fontSize; } } measureText(text, fontSize=64) { if (!this._hasLoaded) { return null; } const lines = Array.isArray(text) ? text : text.split(TEXT_NEWLINE_REGEXP); const font = this.font; const advanceWidthTable = font.hmtx.aWidth; const fontScale = this.getFontScale(fontSize); const descender = font.hhea.descender; const ascender = font.hhea.ascender; const lineGap = font.hhea.lineGap; const lineWidths = lines.map(line => { const glyphs = Typr.U.stringToGlyphs(this.font, line); return glyphs.reduce((advanceWidth, glyphId) => { // stringToGlyphs returns an array on glyph IDs that is the same length as the text string // an ID can sometimes be -1 in cases where multiple characters are merged into a single ligature if (glyphId > -1 && glyphId < advanceWidthTable.length) { advanceWidth += advanceWidthTable[glyphId]; } return advanceWidth; }, 0); }); const width = Math.max(...lineWidths); const lineHeight = (0 - descender) + ascender; const height = lineHeight * lines.length; // Multiply by fontScale to convert from font units to pixels return { width: width * fontScale, height: height * fontScale, lineHeight: lineHeight * fontScale, lineWidths: lineWidths.map(width => width * fontScale), descender: descender * fontScale, ascender: ascender * fontScale, }; } getTextPath(text, fontSize=64, x=0, y=0, z=0, alignX='left', alignY='bottom') { if (!this._hasLoaded) { return []; } const lines = Array.isArray(text) ? text : text.split(TEXT_NEWLINE_REGEXP); const measurements = this.measureText(text, fontSize); const lineWidths = measurements.lineWidths; const lineHeight = measurements.lineHeight; return lines.map((line, lineIndex) => { const [_x, _y, _z] = this.getTextOrigin({ ...measurements, width: lineWidths[lineIndex], }, x, y, z, alignX, alignY); y += lineHeight; const glyphs = Typr.U.stringToGlyphs(this.font, line); const path = Typr.U.glyphsToPath(this.font, glyphs); return this._convertPathCommands(path, fontSize, _x, _y, z); }).flat(); } getTextGlyphs(text, fontSize=64, x=0, y=0, z=0, alignX='left', alignY='bottom') { if (!this._hasLoaded) { return []; } const measurements = this.measureText(text, fontSize); const advanceWidthTable = this.font.hmtx.aWidth; const fontScale = this.getFontScale(fontSize); const lineWidths = measurements.lineWidths; const lineHeight = measurements.lineHeight; const lines = Array.isArray(text) ? text : text.split(TEXT_NEWLINE_REGEXP); return lines.map((line, lineIndex) => { const glyphs = Typr.U.stringToGlyphs(this.font, line); let [_x, _y, _z] = this.getTextOrigin({ ...measurements, width: lineWidths[lineIndex] }, x, y, z, alignX, alignY); y += lineHeight; return glyphs.filter(glyph => glyph !== -1).map(glyphId => { const path = Typr.U.glyphToPath(this.font, glyphId); const shape = { translate: {x:_x, y: _y, z:_z}, path: this._convertPathCommands(path, fontSize, 0, 0, 0) }; _x += advanceWidthTable[glyphId] * fontScale; return shape; }); }).flat(); } getTextOrigin(measuement, x=0, y=0, z=0, alignX='left', alignY='bottom') { let { width, height, lineHeight } = measuement; switch (alignX) { case 'right': x -= width; break; case 'center': x -= width / 2; break; default: break; } switch (alignY) { case 'middle': y -= (height / 2) - lineHeight; break; case 'bottom': default: y -= height - lineHeight; break; } return [x, y, z]; } // Convert Typr.js path commands to Zdog commands // Also apply font size scaling and coordinate adjustment // https://github.com/photopea/Typr.js // https://zzz.dog/shapes#shape-path-commands _convertPathCommands(path, fontSize, x=0, y=0, z=0) { const yDir = -1; const xDir = 1; const fontScale = this.getFontScale(fontSize); const commands = path.cmds; // Apply font scale to all coords const coords = path.crds.map(coord => coord * fontScale); // Convert coords to Zdog commands let startCoord = null; let coordOffset = 0; return commands.map((cmd) => { let result = null; if (!startCoord) { startCoord = {x: x + coords[coordOffset] * xDir, y: y + coords[coordOffset + 1] * yDir, z}; } switch (cmd) { case 'M': // moveTo command result = { move: {x: x + coords[coordOffset] * xDir, y: y + coords[coordOffset + 1] * yDir, z} }; coordOffset += 2; return result; case 'L': // lineTo command result = { line: {x: x + coords[coordOffset] * xDir, y: y + coords[coordOffset + 1] * yDir, z} }; coordOffset += 2; return result; case 'C': // curveTo command result = { bezier: [ {x: x + coords[coordOffset] * xDir, y: y + coords[coordOffset + 1] * yDir, z}, {x: x + coords[coordOffset + 2] * xDir, y: y + coords[coordOffset + 3] * yDir, z}, {x: x + coords[coordOffset + 4] * xDir, y: y + coords[coordOffset + 5] * yDir, z}, ] }; coordOffset += 6; return result; case 'Q': // arcTo command result = { arc: [ {x: x + coords[coordOffset] * xDir, y: y + coords[coordOffset + 1] * yDir, z}, {x: x + coords[coordOffset + 2] * xDir, y: y + coords[coordOffset + 3] * yDir, z}, ] }; coordOffset += 4; return result; case 'Z': // close path if (startCoord) { result = { line: startCoord }; startCoord = null; } return result; // unhandled type // currently, #rrggbb and X types (used in multicolor fonts) aren't supported default: return result; } }).filter(cmd => cmd !== null); // filter out null commands } _fetchFontResource(source) { return new Promise((resolve, reject) => { const request = new XMLHttpRequest(); // Fetch as an arrayBuffer for Typr.parse request.responseType = 'arraybuffer'; request.open('GET', source, true); request.onreadystatechange = e => { if (request.readyState === 4) { if (request.status >= 200 && request.status < 300) { resolve(request.response); } else { reject(`HTTP error ${request.status}: ${request.statusText}`); } } }; request.send(null); }); } } Zdog.Font = ZdogFont; return Zdog; } ================================================ FILE: src/ZdogText.js ================================================ export function registerTextClass(Zdog) { // Zdog.Text class class ZdogText extends Zdog.Shape { constructor(props) { // Set missing props to default values props = Zdog.extend({ font: null, value: '', fontSize: 64, textAlign: 'left', textBaseline: 'bottom', }, props); // Split props const { font, value, fontSize, textAlign, textBaseline, ...shapeProps } = props; // Create shape object super({ ...shapeProps, closed: true, visible: false, // hide until font is loaded path: [{}] }); this._font = null; this._value = value; this._fontSize = fontSize; this._textAlign = textAlign; this._textBaseline = textBaseline; this.font = font; } updateText() { let path = this.font.getTextPath(this.value, this.fontSize, 0, 0, 0, this.textAlign, this.textBaseline); if (path.length == 0) { // zdog doesn't know what to do with empty path arrays this.path = [{}]; this.visible = false; } else { this.path = path; this.visible = true; } this.updatePath(); } set font(newFont) { this._font = newFont; this.font.waitForLoad().then(() => { this.updateText(); this.visible = true; // Find root Zdog.Illustration instance let root = this.addTo; while (root.addTo !== undefined) { root = root.addTo; } // Update render graph if (root && typeof root.updateRenderGraph === 'function') { root.updateRenderGraph(); } }); } get font() { return this._font; } set value(newValue) { this._value = newValue; this.updateText(); } get value() { return this._value; } set fontSize(newSize) { this._fontSize = newSize; this.updateText(); } get fontSize() { return this._fontSize; } set textAlign(newValue) { this._textAlign = newValue; this.updateText(); } get textAlign() { return this._textAlign; } set textBaseline(newValue) { this._textBaseline = newValue; this.updateText(); } get textBaseline() { return this._textBaseline; } } ZdogText.optionKeys = ZdogText.optionKeys.concat(['font', 'fontSize', 'value', 'textAlign', 'textBaseline']); Zdog.Text = ZdogText; return Zdog; } ================================================ FILE: src/ZdogTextGroup.js ================================================ export function registerTextGroupClass(Zdog) { // Zdog.TextGroup class class ZdogTextGroup extends Zdog.Group { constructor(props) { // Set missing props to default values props = Zdog.extend({ font: null, value: '', fontSize: 64, textAlign: 'left', textBaseline: 'bottom', color: '#333', fill: false, stroke: 1, }, props); // Split props const { font, value, fontSize, textAlign, textBaseline, color, fill, stroke, ...groupProps } = props; // Create group object super({ ...groupProps, visible: false, // hide until font is loaded }); this._font = null; this._value = value; this._fontSize = fontSize; this._textAlign = textAlign; this._textBaseline = textBaseline; this._color = color; this._fill = fill; this._stroke = stroke; this.font = font; } updateText() { // Remove old children while (this.children.length > 0) { this.removeChild(this.children[0]); } // Get text paths for each glyph const glyphs = this.font.getTextGlyphs(this.value, this.fontSize, 0, 0, 0, this.textAlign, this.textBaseline); // Convert glyphs to new shapes glyphs.filter(shape => shape.path.length > 0).forEach(shape => { this.addChild(new Zdog.Shape({ translate: shape.translate, path: shape.path, color: this.color, fill: this.fill, stroke: this.stroke, closed: true, })); }); this.updateFlatGraph(); } set font(newFont) { this._font = newFont; this._font.waitForLoad().then(() => { this.updateText(); this.visible = true; // Find root Zdog.Illustration instance let root = this.addTo; while (root.addTo !== undefined) { root = root.addTo; } // Update render graph if (root && typeof root.updateRenderGraph === 'function') { root.updateRenderGraph(); } }); } get font() { return this._font; } set value(newValue) { this._value = newValue; this.updateText(); } get value() { return this._value; } set fontSize(newSize) { this._fontSize = newSize; this.updateText(); } get fontSize() { return this._fontSize; } set textAlign(newValue) { this._textAlign = newValue; this.updateText(); } get textAlign() { return this._textAlign; } set textBaseline(newValue) { this._textBaseline = newValue; this.updateText(); } get textBaseline() { return this._textBaseline; } set color(newColor) { this._color = newColor; this.children.forEach(child => child.color = newColor); } get color() { return this._color; } set fill(newFill) { this._fill = newFill; this.children.forEach(child => child.fill = newFill); } get fill() { return this._fill; } set stroke(newStroke) { this._stroke = newStroke; this.children.forEach(child => child.stroke = newStroke); } get stroke() { return this._stroke; } } ZdogTextGroup.optionKeys = ZdogTextGroup.optionKeys.concat(['color', 'fill', 'stroke', 'font', 'fontSize', 'value', 'textAlign', 'textBaseline']); Zdog.TextGroup = ZdogTextGroup; return Zdog; } ================================================ FILE: src/index.js ================================================ import { registerFontClass } from './ZdogFont'; import { registerTextClass } from './ZdogText'; import { registerTextGroupClass } from './ZdogTextGroup'; export default { init(Zdog) { // Global font list to keep track of all fonts Zdog.FontList = []; // Helper to wait for all fonts to load Zdog.waitForFonts = function() { return Promise.all(Zdog.FontList.map(font => font.waitForLoad())); }; // Register Zfont classes onto the Zdog object registerFontClass(Zdog); registerTextClass(Zdog); registerTextGroupClass(Zdog); return Zdog; }, version: VERSION, } // add dev server flag to the window object // this will be stripped in production builds if (DEV_SERVER) { window.isDevServer = true; }