hersheytext
Advanced tools
Comparing version 1.1.0 to 2.0.0
/** | ||
* @file The node module for easily including the letter data or rendering text. | ||
* @file HersheyText node module for easily including the letter data or rendering text. | ||
*/ | ||
@@ -11,108 +11,8 @@ const cheerio = require('cheerio'); | ||
// Rewrite internal reference filename for SVG fonts to the absolute path. | ||
Object.entries(exports.svgFonts).forEach(([key, item]) => { | ||
item.file = path.join(__dirname, '..', 'svg_fonts', item.file); | ||
}); | ||
/** | ||
* Try to clean up an SVG d value so it's easier to parse it. | ||
* | ||
* @param {string} d | ||
* SVG path d string value to be adjusted. | ||
* | ||
* @returns {string} | ||
* An adjusted d string with spaced around control chars. | ||
*/ | ||
function cleanD(d) { | ||
const out = []; | ||
let lastGood = true; | ||
[...d].forEach((char, index) =>{ | ||
// If this isn't a space, period or negative... | ||
if (![' ', '.', '-'].includes(char)) { | ||
// If this is a parsable number... | ||
if (!Number.isNaN(Number.parseInt(char, 10))) { | ||
// If this number is immediately preceeded by a letter, add a space. | ||
if (!lastGood) { | ||
out.push(' '); | ||
} | ||
lastGood = true; | ||
} else { | ||
// Guaranteed a control char like L or M | ||
// Add a space before it. | ||
out.push(' '); | ||
lastGood = false; | ||
} | ||
} else { | ||
// If this negative is immediately preceeded by a letter, add a space. | ||
if (char === '-' && !lastGood) { | ||
out.push(' '); | ||
} | ||
// These chars can preceed a number. | ||
lastGood = true; | ||
} | ||
out.push(char); | ||
}); | ||
return out.join('').trim().replace(/ /g, ' '); | ||
} | ||
/** | ||
* Scale an SVG font glyph down (and flip the Y axis) | ||
* | ||
* @param {string} d | ||
* SVG path d string value to be adjusted. | ||
* @param {number} height | ||
* Maximum height for a char, used to flip along the Y axis. | ||
* @param {number} scale | ||
* Value that will be multiplied against each x/y value to scale it. | ||
* | ||
* @returns {string} | ||
* An adjusted d string with correct Y flip and given scale applied. | ||
*/ | ||
function glyphScale(d, height, scale) { | ||
if (!d) { | ||
return d; | ||
}; | ||
const pathInput = cleanD(d); | ||
const vals = pathInput.split(' '); | ||
const out = []; | ||
let lastVal = null; | ||
let lastOp = null; // Holds last move/line operation. | ||
let lastCoord = 'x'; // Flops between x and y. | ||
vals.forEach((val, index) => { | ||
let pf = Number.parseFloat(val); | ||
// Either a Move/Line | ||
if (Number.isNaN(pf)) { | ||
lastCoord = 'x'; | ||
// Might have no space, try to parse | ||
if (val.length > 1) { | ||
lastOp = val.subStr(0, 1); | ||
out.push(lastOp); | ||
pf = Number.parseFloat(val.substr(1)); | ||
} else { | ||
lastOp = val; | ||
out.push(lastOp); | ||
} | ||
} | ||
// When we actually have a number! | ||
if (!Number.isNaN(pf)) { | ||
if (lastCoord === 'x') { | ||
pf = Math.round(pf * scale * 100) / 100; | ||
out.push(pf); | ||
lastCoord = 'y'; // We know y is right after | ||
} else if (lastCoord === 'y') { | ||
pf = Math.round((height - pf) * scale * 100) / 100; | ||
out.push(pf); | ||
} | ||
} | ||
}); | ||
return out.join(' '); | ||
} | ||
/** | ||
* Helper to load font data from SVG fonts or original Hershey JSON data. | ||
@@ -134,3 +34,8 @@ * | ||
if (exports.fonts[fontName]) { | ||
font.info = exports.fonts[fontName].name; | ||
font.type = 'hershey'; | ||
font.info = { | ||
'font-family': exports.fonts[fontName].name, // Font title. | ||
'units-per-em': 10, // Height. | ||
'horiz-adv-x': 10, // Space/Default width. | ||
}; | ||
font.chars = exports.fonts[fontName].chars; | ||
@@ -140,13 +45,15 @@ | ||
font.getChar = (char) => { | ||
return font.chars[char.charCodeAt(0) - 33]; | ||
const data = font.chars[char.charCodeAt(0) - 33]; | ||
if (data) { | ||
return { type: char, name: char, width: parseInt(data.o, 10), d: data.d }; | ||
} | ||
return null; | ||
}; | ||
} else if (exports.svgFonts[fontName]) { | ||
const glyphScaling = 0.1; | ||
const data = fs.readFileSync( | ||
path.join(__dirname, '..', 'svg_fonts', exports.svgFonts[fontName].file) | ||
); | ||
const data = fs.readFileSync(exports.svgFonts[fontName].file); | ||
const $ = cheerio.load(data, { xmlMode: true }); | ||
font.info = $('font-face').attr(); | ||
font.type = 'svg'; | ||
font.info = { ...$('font-face').attr(), ...$('font').attr() }; | ||
font.chars = {}; | ||
@@ -156,10 +63,8 @@ | ||
const item = $('glyph')[index]; | ||
// Add all glyphs except space. | ||
if (item.attribs.unicode !== ' ') { | ||
font.chars[item.attribs.unicode] = { | ||
name: item.attribs['glyph-name'], | ||
width: item.attribs['horiz-adv-x'] * glyphScaling, | ||
o: item.attribs['horiz-adv-x'] * glyphScaling, | ||
d: glyphScale(item.attribs.d, font.info['units-per-em'], glyphScaling), | ||
} | ||
// Add all glyphs including space (which only contains the space width!). | ||
font.chars[item.attribs.unicode] = { | ||
type: item.attribs.unicode, | ||
name: item.attribs['glyph-name'], | ||
width: parseFloat(item.attribs['horiz-adv-x']) || parseFloat(font.info['horiz-adv-x']), | ||
d: item.attribs.d || null, | ||
} | ||
@@ -175,3 +80,25 @@ }); | ||
// Export the get data function. | ||
exports.getFontData = getFontData; | ||
/** | ||
* Add a new SVG Font via file location. | ||
* | ||
* @returns {boolean} | ||
* True if it worked, false if not. | ||
*/ | ||
exports.addSVGFont = (file) => { | ||
try { | ||
const data = fs.readFileSync(file); | ||
const $ = cheerio.load(data, { xmlMode: true }); | ||
const name = $('font-face').attr('font-family'); | ||
const cleanName = name.toLowerCase().replace(/\s/g, '_'); | ||
exports.svgFonts[cleanName] = { name, file }; | ||
} catch (error) { | ||
console.error(error); | ||
return false; | ||
} | ||
}; | ||
/** | ||
* Get a flat array list of machine name valid fonts. | ||
@@ -191,11 +118,8 @@ * | ||
* Object of named options: | ||
* font {string}: [Optional] Name of font face from main font object, defaults to "futural", Sans 1 Stroke | ||
* id {string}: [Required] ID to give the final g(roup) SVG DOM object | ||
* pos {object}: [Required] {X, Y} object of where to place the final object within the SVG | ||
* charWidth {int}: [Optional] Base width given to each character | ||
* charHeight {int}: [Optional] Base height given to each character (when wrapping) | ||
* scale {int}: [Optional] Scale to multiply size of everything by | ||
* wrapWidth {int}: [Optional] Max line size at which to wrap to the next line | ||
* centerWidth {int}: [Optional] Width to center multiline text inside of | ||
* centerHeight {int}: [Optional] Height to center text inside of vertically | ||
* font {string}: Name of font face from main font object | ||
* id {string}: ID to give the final g(roup) SVG DOM object | ||
* pos {object}: {x, y} object of where to position the object | ||
* charSpacingAdjust {int}: Amount to add or remove from between char spacing. | ||
* charHeightAdjust {int}: Amount to add or remove from line height. | ||
* scale {int}: Scale to multiply size of everything by | ||
* | ||
@@ -207,12 +131,12 @@ * @returns {string|boolean} | ||
exports.renderTextSVG = function(text, rawOptions = {}) { | ||
const defaults = { | ||
const options = { | ||
id: 'text', | ||
font: 'futural', | ||
charWidth: 10, | ||
charHeight: 28, | ||
scale: 1, | ||
charSpacingAdjust: 0, | ||
charHeightAdjust: 0, | ||
scale: 2, | ||
pos: { x: 0, y: 0 }, | ||
...rawOptions, | ||
}; | ||
// Merge defaults with argument options. | ||
const options = {...defaults, ...rawOptions}; | ||
// Prep SVG export. | ||
@@ -223,2 +147,3 @@ const $ = cheerio.load('<g>'); // Initial DOM | ||
const font = getFontData(options.font); | ||
const multiplyer = font.type === 'svg' ? 1 : 1.68; | ||
const offset = { left: 0, top: 0 }; | ||
@@ -248,60 +173,32 @@ | ||
for(let i in word) { | ||
const char = font.getChar(word[i]); | ||
const rawChar = word[i]; | ||
const char = font.getChar(rawChar); | ||
// Only print in range chars | ||
let charOffset = options.charWidth; | ||
if (char){ | ||
charOffset = parseInt(char.o, 10); | ||
if (char) { | ||
const $path = $('<path>').attr({ | ||
d: char.d, | ||
stroke: 'black', | ||
'stroke-width': 1, | ||
fill: 'none', | ||
transform: `translate(${offset.left}, ${offset.top})`, | ||
letter: word[i] | ||
}); | ||
// Add the char to the DOM | ||
$groupLine.append( | ||
$('<path>').attr({ | ||
d: char.d, | ||
stroke: 'black', | ||
'stroke-width': 2, | ||
fill: 'none', | ||
transform: `translate(${offset.left}, ${offset.top})`, | ||
letter: word[i] | ||
}) | ||
); | ||
if (font.type === 'svg') { | ||
$path.attr('transform', `translate(${offset.left}, ${font.info['units-per-em']}) scale(1, -1)`); | ||
} | ||
// Add the char to the DOM group. | ||
$groupLine.append($path); | ||
// Position next character. | ||
offset.left += (char.width * multiplyer) + options.charSpacingAdjust; | ||
} | ||
// Add space between | ||
offset.left += charOffset + options.charWidth; | ||
} | ||
// Wrap words to width | ||
if (options.wrapWidth) { | ||
if (offset.left > options.wrapWidth) { | ||
if (options.centerWidth) { | ||
const c = (options.centerWidth / 2) - (offset.left / 2); | ||
$groupLine.attr('transform', `translate(${c},0)`); | ||
} | ||
offset.left = 0; | ||
offset.top += options.charHeight; | ||
// New Line container | ||
lineCount++; | ||
$groupLine = $('<g>').attr('id', `${options.id}-line-${lineCount}`); | ||
$textGroup.append($groupLine); | ||
} else { | ||
offset.left += options.charWidth * 2; // Add regular space | ||
} | ||
} else { | ||
offset.left += options.charWidth * 2; // Add regular space | ||
} | ||
// Word boundary: Add a space. | ||
offset.left+= parseInt(font.info['horiz-adv-x'], 10) + options.charSpacingAdjust; | ||
} | ||
if (options.centerWidth) { | ||
const c = (options.centerWidth / 2) - (offset.left / 2); | ||
$groupLine.attr('transform', `translate(${c},0)`); | ||
} | ||
if (options.centerHeight) { | ||
const c = (options.centerHeight / 2) - ((options.charHeight * (lineCount + 1)) / 2) + options.pos.y; | ||
$textGroup.attr({ | ||
transform: `scale(${options.scale}) translate(${options.pos.x}, ${c})` | ||
}); | ||
} | ||
} catch(e) { | ||
@@ -322,3 +219,3 @@ console.error(e); | ||
* Object of named options: | ||
* font {string}: [Optional] Name of font face from main font object, defaults to "futural", Sans 1 Stroke | ||
* font {string}: [Optional] Name of font face from main font object | ||
* | ||
@@ -330,10 +227,5 @@ * @returns {string|boolean} | ||
const out = []; | ||
const defaults = { | ||
font: 'futural', | ||
}; | ||
const options = { font: 'futural', ...rawOptions }; | ||
// Merge defaults with argument options. | ||
const options = {...defaults, ...rawOptions}; | ||
// Change CRLF with just LF | ||
// Change CRLF with just LF. | ||
text = text.replace(/\r\n/g, "\n"); | ||
@@ -343,13 +235,16 @@ try { | ||
// Move through each letter | ||
// Move through each letter. | ||
for(let i in text) { | ||
const char = font.getChar(text[i]); | ||
// Only print in range chars | ||
// Only print in range chars. | ||
if (char){ | ||
out.push({type: text[i], d: char.d, o: char.o}); | ||
out.push(char); | ||
} else if (text[i] === "\n") { | ||
out.push({type: 'newline'}); | ||
}else if (text[i] === ' ') { | ||
out.push({type: 'space'}); | ||
out.push({ name: 'newline', width: 0 }); | ||
} else { | ||
// Add a space if char not found. | ||
// TODO: Add support for "missing-glyph". | ||
// TODO: This might break for hershey font. Please use SVG font, thank you! | ||
out.push(font.getChar(' ')); | ||
} | ||
@@ -356,0 +251,0 @@ } |
{ | ||
"name": "hersheytext", | ||
"description": "A port of the Hershey engraving fonts to JSON for JavaScript/SVG", | ||
"version": "1.1.0", | ||
"version": "2.0.0", | ||
"main": "lib/hersheytext.js", | ||
@@ -28,4 +28,4 @@ "maintainers": [ | ||
"dependencies": { | ||
"cheerio": "~0.19.0" | ||
"cheerio": "^0.22.0" | ||
} | ||
} |
114
README.md
@@ -1,3 +0,2 @@ | ||
Hershey Text JS | ||
============= | ||
# Hershey Text JS | ||
@@ -12,13 +11,110 @@ A port of the | ||
### Node.js Usage | ||
JSON data only contains original Hershey font data, but Node.js module allows access to | ||
this and all included SVG fonts and an API to add your own! | ||
## Clientside Use | ||
Load the structured JSON data and use it to create your own renderer, or use the example | ||
as a starting point. | ||
## Node.js: Install & Include | ||
Install via `npm install hersheytext`, then include in your node script with | ||
`var hersheyText = require('hersheytext');`. This will give you access to the | ||
fonts at `hersheyText.fonts`, EG `hersheyText.fonts['futural'].chars[2]`. The | ||
`chars[n].d` string value can be put directly into the `d` attribute within a | ||
`<path>` SVG element, or imported to a Paper.js path. | ||
`const hershey = require('hersheytext');`. | ||
See hersheytest.js for more usage examples, check `lib/hersheytext.js` for full | ||
function documentation. | ||
### JSON data source | ||
The JSON font data will be accessible at `hershey.fonts`, EG: | ||
`hershey.fonts['futural'].chars[2]`. The `chars[n].d` string value can be put directly | ||
into the `d` attribute within a `<path>` SVG element, or imported to a | ||
[Paper.js compound path](http://paperjs.org/reference/compoundpath). | ||
See hersheytest.js for more data usage examples, check `lib/hersheytext.js` for full | ||
function level documentation. | ||
### Get raw font data | ||
You can access either SVG or Hershey font raw data using `getFontData()`: | ||
```javascript | ||
const hershey = require('hersheytext'); | ||
console.log('All fonts available', hershey.getFonts()); | ||
const svgFont = hershey.getFontData('ems_tech'); | ||
console.log('Font Type:', svgFont.type); | ||
console.log('SVG <font-face> cap height:', svgFont.info['cap-height']); | ||
console.log('SVG <font> default char kern width:', svgFont.info['horiz-adv-x']); | ||
console.log('SVG <glyph> exclamation char data:', svgFont.getChar('!')); | ||
const hersheyFont = hershey.getFontData('cursive'); | ||
console.log('Font Type:', hersheyFont.type); | ||
console.log('Hershey Font Display Name:', hersheyFont.info['font-family']); | ||
console.log(`Hershey Font 'A' kern width:`, hersheyFont.getChar('A').width); | ||
console.log('Hershey Font exclamation char data:', hersheyFont.getChar('!').d); | ||
``` | ||
### Render to SVG directly | ||
Hershey Text JS comes with a very simple single line renderer, `renderTextSVG()`, which | ||
returns a formatted group of characters with correct offsets. | ||
```javascript | ||
const hershey = require('hersheytext'); | ||
const fs = require('fs'); | ||
const options = { | ||
font: 'hershey_script_1', | ||
scale: 0.25, | ||
}; | ||
const header = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">'; | ||
const data = hershey.renderTextSVG('Take a Look, if you Will.', options); | ||
const footer = '</svg>'; | ||
fs.writeFileSync('test.svg', `${header}\n${data}\n${footer}`); | ||
``` | ||
### Make your own renderer with text array | ||
If you have your own rendering intent that isn't SVG directly, the render array function | ||
is the easiest way to do that. To render plain text into an array of easily renderable | ||
character data from SVG or Hershey font data, start with something like this: | ||
```javascript | ||
const hershey = require('hersheytext'); | ||
const textArray = hershey.renderTextArray('Hello World!', { font: 'ems_allure' }); | ||
textArray.forEach(char => { | ||
console.log('--------------------------------------------'); | ||
console.log('Character:', char.type); | ||
console.log('Name:', char.name); | ||
console.log('Kerning Width (in font units):', char.width); | ||
console.log('Path Data:', char.d); | ||
}); | ||
``` | ||
### Add a custom SVG font | ||
If you have an SVG font you'd like to use that isn't one of the ones HersheyText JS ships | ||
with, just use `addSVGFont()`: | ||
```javascript | ||
const hershey = require('hersheytext'); | ||
// Will return false if there was a problem. See console for errors. | ||
hershey.addSVGFont('path/to/my-test-font.svg'); | ||
// Machine name will be SVG font-family, lowercase with spaces replaced with underscores. | ||
const options = { font: 'my_test_font' }; | ||
const data = hershey.renderTextSVG('In my own custom font!', options); | ||
``` | ||
**Notes:** | ||
* Raw SVG font data is in glyph format, and therefore needs to be Y-inverted after | ||
it's been positioned, EG: `<path transform="scale(1, -1)" …` | ||
* Hershey font data is in its own far smaller scale and does not need to be inverted. | ||
-------- | ||
JSON data Public Domain, All other code MIT Licensed. |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1875024
120
10793
+ Addedcheerio@0.22.0(transitive)
+ Addedcss-select@1.2.0(transitive)
+ Addedcss-what@2.1.3(transitive)
+ Addeddomhandler@2.4.2(transitive)
+ Addedhtmlparser2@3.10.1(transitive)
+ Addedlodash.assignin@4.2.0(transitive)
+ Addedlodash.bind@4.2.1(transitive)
+ Addedlodash.defaults@4.2.0(transitive)
+ Addedlodash.filter@4.6.0(transitive)
+ Addedlodash.flatten@4.4.0(transitive)
+ Addedlodash.foreach@4.5.0(transitive)
+ Addedlodash.map@4.6.0(transitive)
+ Addedlodash.merge@4.6.2(transitive)
+ Addedlodash.pick@4.4.0(transitive)
+ Addedlodash.reduce@4.6.0(transitive)
+ Addedlodash.reject@4.6.0(transitive)
+ Addedlodash.some@4.6.0(transitive)
+ Addedreadable-stream@3.6.2(transitive)
+ Addedsafe-buffer@5.2.1(transitive)
+ Addedstring_decoder@1.3.0(transitive)
+ Addedutil-deprecate@1.0.2(transitive)
- Removedcheerio@0.19.0(transitive)
- Removedcore-util-is@1.0.3(transitive)
- Removedcss-select@1.0.0(transitive)
- Removedcss-what@1.0.0(transitive)
- Removeddomhandler@2.3.0(transitive)
- Removeddomutils@1.4.3(transitive)
- Removedentities@1.0.0(transitive)
- Removedhtmlparser2@3.8.3(transitive)
- Removedisarray@0.0.1(transitive)
- Removedlodash@3.10.1(transitive)
- Removedreadable-stream@1.1.14(transitive)
- Removedstring_decoder@0.10.31(transitive)
Updatedcheerio@^0.22.0