Skip to content

Commit

Permalink
feat(modeling): corrected vectorChar and vectorText options to align …
Browse files Browse the repository at this point in the history
…with the common API
  • Loading branch information
platypii committed Sep 16, 2023
1 parent 29788a1 commit c3a950f
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 97 deletions.
6 changes: 1 addition & 5 deletions packages/modeling/src/text/vectorChar.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ export interface VectorCharOptions {
yOffset?: number
height?: number
extrudeOffset?: number
input?: string
}

export function vectorChar(): VectorChar
export function vectorChar(char: string): VectorChar
export function vectorChar(options: VectorCharOptions): VectorChar
export function vectorChar(options: Omit<VectorCharOptions, 'input'>, char: string): VectorChar
export function vectorChar(options: VectorCharOptions, text: string): VectorChar
36 changes: 21 additions & 15 deletions packages/modeling/src/text/vectorChar.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { fromPoints } from '../geometries/path2/index.js'
import * as vec2 from '../maths/vec2/index.js'

import { vectorParams } from './vectorParams.js'
import { simplex } from './fonts/single-line/hershey/simplex.js'

const defaultsVectorParams = {
xOffset: 0,
yOffset: 0,
font: simplex,
height: 14, // old vector_xxx simplex font height
extrudeOffset: 0
}

/**
* Represents a character as an anonymous object containing a list of 2D paths.
Expand All @@ -14,31 +23,28 @@ import { vectorParams } from './vectorParams.js'
* Construct a {@link VectorChar} from an ASCII character whose code is between 31 and 127.
* If the character is not supported it is replaced by a question mark.
*
* @param {object} [options] - options for construction
* @param {object} options - options for text construction
* @param {number} [options.xOffset=0] - x offset
* @param {number} [options.yOffset=0] - y offset
* @param {number} [options.height=21] - font size/character height (uppercase height)
* @param {number} [options.extrudeOffset=0] - width of the extrusion that will be applied (manually) after the creation of the character
* @param {string} [options.input='?'] - ascii character (ignored/overwritten if provided as second parameter)
* @param {string} [character='?'] - ascii character
* @param {string} text - ascii character
* @returns {VectorChar} a new vertor char object
* @alias module:modeling/text.vectorChar
*
* @example
* let mycharacter = vectorChar()
* or
* let mycharacter = vectorChar('A')
* or
* let mycharacter = vectorChar({ xOffset: 57 }, 'C')
* or
* let mycharacter = vectorChar({ xOffset: 78, input: '!' })
*/
export const vectorChar = (options, character) => {
export const vectorChar = (options, text) => {
const {
xOffset, yOffset, input, font, height, extrudeOffset
} = vectorParams(options, character)
xOffset, yOffset, font, height, extrudeOffset
} = Object.assign({}, defaultsVectorParams, options)

if (typeof text !== 'string' || text.length !== 1) {
throw new Error('text must be a single character')
}

let code = input.charCodeAt(0)
let code = text.charCodeAt(0)
if (!code || !font[code]) {
code = 63 // invalid character so use ?
}
Expand All @@ -54,7 +60,7 @@ export const vectorChar = (options, character) => {
const gx = ratio * glyph[i] + xOffset
const gy = ratio * glyph[i + 1] + yOffset + extrudeYOffset
if (glyph[i] !== undefined) {
polyline.push([gx, gy])
polyline.push(vec2.fromValues(gx, gy))
continue
}
paths.push(fromPoints({}, polyline))
Expand Down
24 changes: 8 additions & 16 deletions packages/modeling/src/text/vectorChar.test.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import test from 'ava'

import { vec2 } from '../maths/index.js'
import { path2 } from '../geometries/index.js'

import { vectorChar } from './index.js'

const questionMarkSegments = [ // '?'
[[3, 16], [3, 17], [4, 19], [5, 20], [7, 21], [11, 21], [13, 20], [14, 19], [15, 17], [15, 15], [14, 13], [13, 12], [9, 10], [9, 7]],
[[9, 2], [8, 1], [9, 0], [10, 1]]
]

const compareSegments = (paths, segments) => {
if (paths.length !== segments.length) return false
for (let i = 0; i < paths.length; i++) {
Expand All @@ -23,16 +17,8 @@ const compareSegments = (paths, segments) => {
return true
}

test('vectorChar (defaults)', (t) => {
const obs = vectorChar()

t.deepEqual(obs.width, 18)
t.deepEqual(obs.height, 14)
t.true(compareSegments(obs.paths, questionMarkSegments))
})

test('vectorChar (char)', (t) => {
const obs = vectorChar('H')
const obs = vectorChar({}, 'H')
const expSegments = [
[[4, 21], [4, 0]],
[[18, 21], [18, 0]],
Expand All @@ -58,7 +44,7 @@ test('vectorChar ({ xOffset, yOffset }, char)', (t) => {
})

test('vectorChar ({ height }, char)', (t) => {
const obs = vectorChar({ height: 10, input: 'h' })
const obs = vectorChar({ height: 10 }, 'h')
const expSegments = [
[[2.857142857142857, 15], [2.857142857142857, 0]],
[[2.857142857142857, 7.142857142857143], [5, 9.285714285714286], [6.428571428571429, 10], [8.571428571428571, 10], [10, 9.285714285714286], [10.714285714285715, 7.142857142857143], [10.714285714285715, 0]]
Expand All @@ -69,3 +55,9 @@ test('vectorChar ({ height }, char)', (t) => {
t.true(compareSegments(obs.paths, expSegments))
})

test('vectorChar required options', (t) => {
t.throws(() => vectorChar(), { message: 'text must be a single character' })
t.throws(() => vectorChar({}), { message: 'text must be a single character' })
t.throws(() => vectorChar({}, ''), { message: 'text must be a single character' })
t.throws(() => vectorChar({}, 'ABC'), { message: 'text must be a single character' })
})
24 changes: 0 additions & 24 deletions packages/modeling/src/text/vectorParams.js

This file was deleted.

6 changes: 1 addition & 5 deletions packages/modeling/src/text/vectorText.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ export interface VectorTextOptions {
letterSpacing?: number
align?: 'left' | 'center' | 'right'
extrudeOffset?: number
input?: string
}

export function vectorText(): VectorText
export function vectorText(text: string): VectorText
export function vectorText(options: VectorTextOptions): VectorText
export function vectorText(options: Omit<VectorTextOptions, 'input'>, text: string): VectorText
export function vectorText(options: VectorTextOptions, text: string): VectorText
42 changes: 23 additions & 19 deletions packages/modeling/src/text/vectorText.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import * as mat4 from '../maths/mat4/index.js'
import * as path2 from '../geometries/path2/index.js'

import { simplex } from './fonts/single-line/hershey/simplex.js'
import { vectorChar } from './vectorChar.js'
import { vectorParams } from './vectorParams.js'

const defaultsVectorParams = {
xOffset: 0,
yOffset: 0,
align: 'left',
font: simplex,
height: 14, // old vector_xxx simplex font height
lineSpacing: 30 / 14, // old vector_xxx ratio
letterSpacing: 0, // proportion of font size, i.e. CSS em
extrudeOffset: 0
}

/**
* Represents a line of characters as an anonymous object containing a list of VectorChar.
Expand All @@ -21,9 +32,7 @@ const translateLine = (options, line) => {
mat4.translate(matrix, matrix, [x, y, 0])

line.chars = line.chars.map((vchar) => {
vchar.paths = vchar.paths.map((path) => {
return path2.transform(matrix, path)
})
vchar.paths = vchar.paths.map((path) => path2.transform(matrix, path))
return vchar
})
return line
Expand All @@ -32,32 +41,27 @@ const translateLine = (options, line) => {
/**
* Construct an array of character segments from an ascii string whose characters code is between 31 and 127,
* if one character is not supported it is replaced by a question mark.
* @param {object|string} [options] - options for construction or ascii string
* @param {object} options - options for text construction
* @param {number} [options.xOffset=0] - x offset
* @param {number} [options.yOffset=0] - y offset
* @param {number} [options.height=14] - height of requested characters (uppercase height), i.e. font height in points
* @param {number} [options.lineSpacing=30/14] - line spacing expressed as a percentage of height
* @param {number} [options.letterSpacing=0] - extra letter spacing, expressed as a proportion of height, i.e. like CSS em
* @param {string} [options.align='left'] - multi-line text alignment: left, center, right
* @param {number} [options.extrudeOffset=0] - width of the extrusion that will be applied (manually) after the creation of the character
* @param {string} [options.input='?'] - ascii string (ignored/overwrited if provided as seconds parameter)
* @param {string} [text='?'] - ascii string
* @param {string} text - ascii string
* @returns {Array} list of vector line objects, where each line contains a list of vector character objects
* @alias module:modeling/text.vectorText
*
* @example
* let mylines = vectorText()
* or
* let mylines = vectorText('OpenJSCAD')
* or
* let mylines = vectorText({ yOffset: -50 }, 'OpenJSCAD')
* or
* let mylines = vectorText({ yOffset: -80, input: 'OpenJSCAD' })
* let mylines = vectorText({ yOffset: -50 }, 'JSCAD')
*/
export const vectorText = (options, text) => {
const {
xOffset, yOffset, input, font, height, align, extrudeOffset, lineSpacing, letterSpacing
} = vectorParams(options, text)
xOffset, yOffset, font, height, align, extrudeOffset, lineSpacing, letterSpacing
} = Object.assign({}, defaultsVectorParams, options)

if (typeof text !== 'string') throw new Error('text must be a string')

// NOTE: Just like CSS letter-spacing, the spacing could be positive or negative
const extraLetterSpacing = (height * letterSpacing)
Expand All @@ -78,9 +82,9 @@ export const vectorText = (options, text) => {
let x = xOffset
let y = yOffset
let vchar
let il = input.length
const il = text.length
for (let i = 0; i < il; i++) {
const character = input[i]
const character = text[i]
if (character === '\n') {
pushLine()

Expand All @@ -92,7 +96,7 @@ export const vectorText = (options, text) => {
// convert the character
vchar = vectorChar({ xOffset: x, yOffset: y, font, height, extrudeOffset }, character)

let width = vchar.width + extraLetterSpacing
const width = vchar.width + extraLetterSpacing
x += width

// update current line
Expand Down
36 changes: 23 additions & 13 deletions packages/modeling/src/text/vectorText.test.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import test from 'ava'

import { vectorChar, vectorText } from './index.js'
import { vectorText } from './index.js'

test('vectorText (empty string)', (t) => {
const obs = vectorText({}, '')
t.is(obs.length, 0)
})

test('vectorText (text)', (t) => {
const obs = vectorText('O I') // one char with one closed path, one char with one open path
const obs = vectorText({}, 'O I') // one char with one closed path, one char with one open path
t.is(obs.length, 1)

const vtext = obs[0]
// t.is(vtext.width, 46)
t.is(vtext.width, 46)
t.is(vtext.height, 14)
t.is(vtext.chars.length, 2)

Expand All @@ -34,7 +39,7 @@ test('vectorText (text)', (t) => {

test('vectorText (multi-line-text)', (t) => {
// NOTE: control line spacing to verify '\n' characters are working
const obs = vectorText({lineSpacing: 1.0}, '\nROCKS!\n\nOpen\nJSCAD\nROCKS!\n')
const obs = vectorText({ lineSpacing: 1 }, '\nROCKS!\n\nOpen\nJSCAD\nROCKS!\n')
// only those lines with characters will be returned
t.is(obs.length, 4)

Expand Down Expand Up @@ -80,7 +85,7 @@ test('vectorText ({ yOffset }, text)', (t) => {
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
0, 0, 0, 1
]

const obs = vectorText({ yOffset: 20 }, 'y')
Expand Down Expand Up @@ -108,7 +113,7 @@ test('vectorText ({ xOffset }, text)', (t) => {
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
0, 0, 0, 1
]

const obs = vectorText({ xOffset: 20 }, 'y')
Expand All @@ -132,7 +137,7 @@ test('vectorText ({ xOffset }, text)', (t) => {
})

test('vectorText ({ letterSpacing }, text)', (t) => {
const obs = vectorText({letterSpacing: 0.5}, 'JSCAD')
const obs = vectorText({ letterSpacing: 0.5 }, 'JSCAD')
t.is(obs.length, 1)

const vtext = obs[0]
Expand Down Expand Up @@ -165,19 +170,19 @@ test('vectorText ({ align: center }, text)', (t) => {
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
18.5, 0, 0, 1,
18.5, 0, 0, 1
]
const expectedTransformAB = [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
9, 0, 0, 1,
9, 0, 0, 1
]
const expectedTransformABC = [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
0, 0, 0, 1
]

const obs = vectorText({ align: 'center' }, 'a\nab\nabc')
Expand Down Expand Up @@ -213,19 +218,19 @@ test('vectorText ({ align: right }, text)', (t) => {
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
37, 0, 0, 1,
37, 0, 0, 1
]
const expectedTransformAB = [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
18, 0, 0, 1,
18, 0, 0, 1
]
const expectedTransformABC = [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
0, 0, 0, 1
]

const obs = vectorText({ align: 'right' }, 'a\nab\nabc')
Expand Down Expand Up @@ -255,3 +260,8 @@ test('vectorText ({ align: right }, text)', (t) => {
t.deepEqual(vchar.paths[0].points[0], [15, -46])
t.deepEqual(vchar.paths[0].transforms, expectedTransformABC)
})

test('vectorText required options', (t) => {
t.throws(() => vectorText(), { message: 'text must be a string' })
t.throws(() => vectorText({}), { message: 'text must be a string' })
})

0 comments on commit c3a950f

Please sign in to comment.