New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

troika-three-text

Package Overview
Dependencies
Maintainers
1
Versions
60
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

troika-three-text - npm Package Compare versions

Comparing version 0.33.1 to 0.34.0-textoutline.3

4

package.json
{
"name": "troika-three-text",
"version": "0.33.1",
"version": "0.34.0-textoutline.3+2d03ce4",
"description": "SDF-based text rendering for Three.js",

@@ -28,3 +28,3 @@ "author": "Jason Johnston <jason.johnston@protectwise.com>",

},
"gitHead": "0c397b65aac6137ce1b4417e55d0deeb77b5ee5f"
"gitHead": "2d03ce4e0e9ff18fcc5773bfd91c78e610e509e1"
}

@@ -98,3 +98,3 @@ import {

* the SDF atlas texture.
* @param {Array} totalBounds - An array holding the [minX, minY, maxX, maxY] across all glyphs
* @param {Array} blockBounds - An array holding the [minX, minY, maxX, maxY] across all glyphs
* @param {Array} [chunkedBounds] - An array of objects describing bounds for each chunk of N

@@ -105,3 +105,3 @@ * consecutive glyphs: `{start:N, end:N, rect:[minX, minY, maxX, maxY]}`. This can be

*/
updateGlyphs(glyphBounds, glyphAtlasIndices, totalBounds, chunkedBounds, glyphColors) {
updateGlyphs(glyphBounds, glyphAtlasIndices, blockBounds, chunkedBounds, glyphColors) {
// Update the instance attributes

@@ -117,12 +117,12 @@ updateBufferAttr(this, glyphBoundsAttrName, glyphBounds, 4)

sphere.center.set(
(totalBounds[0] + totalBounds[2]) / 2,
(totalBounds[1] + totalBounds[3]) / 2,
(blockBounds[0] + blockBounds[2]) / 2,
(blockBounds[1] + blockBounds[3]) / 2,
0
)
sphere.radius = sphere.center.distanceTo(tempVec3.set(totalBounds[0], totalBounds[1], 0))
sphere.radius = sphere.center.distanceTo(tempVec3.set(blockBounds[0], blockBounds[1], 0))
// Update the boundingBox based on the total bounds
const box = this.boundingBox;
box.min.set(totalBounds[0], totalBounds[1], 0);
box.max.set(totalBounds[2], totalBounds[3], 0);
box.min.set(blockBounds[0], blockBounds[1], 0);
box.max.set(blockBounds[2], blockBounds[3], 0);
}

@@ -129,0 +129,0 @@

@@ -61,3 +61,3 @@ //=== Utility functions for dealing with carets and selection ranges ===//

const {caretPositions, caretHeight, totalBounds} = textRenderInfo
const {caretPositions, caretHeight, blockBounds} = textRenderInfo

@@ -84,4 +84,4 @@ // Normalize

} else {
row.left = Math.max(Math.min(row.left, x1), totalBounds[0])
row.right = Math.min(Math.max(row.right, x2), totalBounds[2])
row.left = Math.max(Math.min(row.left, x1), blockBounds[0])
row.right = Math.min(Math.max(row.right, x2), blockBounds[2])
}

@@ -88,0 +88,0 @@ }

@@ -213,2 +213,17 @@ import {

/**
* @member {number} outlineWidth
* NOTE: BETA FEATURE, NOT STABLE
* The width, in local units, of an outline drawn around each text glyph using the
* `outlineColor`. Defaults to `0`.
*/
this.outlineWidth = 0
/**
* @member {string|number|THREE.Color} outlineColor
* NOTE: BETA FEATURE, NOT STABLE
* The color of the text outline, if `outlineWidth` is greater than zero. Defaults to black.
*/
this.outlineColor = 0
/**
* @member {number} depthOffset

@@ -304,3 +319,3 @@ * This is a shortcut for setting the material's `polygonOffset` and related properties,

textRenderInfo.glyphAtlasIndices,
textRenderInfo.totalBounds,
textRenderInfo.blockBounds,
textRenderInfo.chunkedBounds,

@@ -406,19 +421,23 @@ textRenderInfo.glyphColors

if (textInfo) {
const {sdfTexture, totalBounds} = textInfo
const {sdfTexture, blockBounds} = textInfo
uniforms.uTroikaSDFTexture.value = sdfTexture
uniforms.uTroikaSDFTextureSize.value.set(sdfTexture.image.width, sdfTexture.image.height)
uniforms.uTroikaSDFGlyphSize.value = textInfo.sdfGlyphSize
uniforms.uTroikaSDFMinDistancePct.value = textInfo.sdfMinDistancePercent
uniforms.uTroikaTotalBounds.value.fromArray(totalBounds)
uniforms.uTroikaSDFExponent.value = textInfo.sdfExponent
uniforms.uTroikaTotalBounds.value.fromArray(blockBounds)
uniforms.uTroikaUseGlyphColors.value = !!textInfo.glyphColors
uniforms.uTroikaOutlineWidth.value = this.outlineWidth || 0
uniforms.uTroikaOutlineColor.value.set(this.outlineColor || 0)
let clipRect = this.clipRect
if (!(clipRect && Array.isArray(clipRect) && clipRect.length === 4)) {
uniforms.uTroikaClipRect.value.fromArray(totalBounds)
if (clipRect && Array.isArray(clipRect) && clipRect.length === 4) {
uniforms.uTroikaClipRect.value.fromArray(clipRect)
} else {
// no clipping - choose a finite rect that shouldn't ever be reached by overflowing glyphs or outlines
const pad = (this.fontSize || 0.1) * 100
uniforms.uTroikaClipRect.value.set(
Math.max(totalBounds[0], clipRect[0]),
Math.max(totalBounds[1], clipRect[1]),
Math.min(totalBounds[2], clipRect[2]),
Math.min(totalBounds[3], clipRect[3])
blockBounds[0] - pad,
blockBounds[1] - pad,
blockBounds[2] + pad,
blockBounds[3] + pad
)

@@ -471,3 +490,3 @@ }

if (textInfo) {
const bounds = textInfo.totalBounds
const bounds = textInfo.blockBounds
raycastMesh.matrixWorld.multiplyMatrices(

@@ -474,0 +493,0 @@ this.matrixWorld,

@@ -15,2 +15,4 @@ import { Color, DataTexture, LinearFilter, LuminanceFormat } from 'three'

sdfGlyphSize: 64,
sdfMargin: 1 / 16,
sdfExponent: 9,
textureWidth: 2048

@@ -34,2 +36,9 @@ }

* to 64 which is generally a good balance of size and quality.
* @param {Number} config.sdfExponent - The exponent used when encoding the SDF values. A higher exponent
* shifts the encoded 8-bit values to achieve higher precision/accuracy at texels nearer
* the glyph's path, with lower precision further away. Defaults to 9.
* @param {Number} config.sdfMargin - How much space to reserve in the SDF as margin outside the glyph's
* path, as a percentage of the SDF width. A larger margin increases the quality of
* extruded glyph outlines, but decreases the precision available for the glyph itself.
* Defaults to 1/16th of the glyph size.
* @param {Number} config.textureWidth - The width of the SDF texture; must be a power of 2. Defaults to

@@ -50,15 +59,3 @@ * 2048 which is a safe maximum texture dimension according to the stats at

/**
* The radial distance from glyph edges over which the SDF alpha will be calculated; if the alpha
* at distance:0 is 0.5, then the alpha at this distance will be zero. This is defined as a percentage
* of each glyph's maximum dimension in font space units so that it maps to the same minimum number of
* SDF texels regardless of the glyph's size. A larger value provides greater alpha gradient resolution
* and improves readability/antialiasing quality at small display sizes, but also decreases the number
* of texels available for encoding path details.
*/
const SDF_DISTANCE_PERCENT = 1 / 8
/**
* Repository for all font SDF atlas textures

@@ -78,4 +75,4 @@ *

* @property {DataTexture} sdfTexture - The SDF atlas texture.
* @property {number} sdfGlyphSize - The size of each glyph's SDF.
* @property {number} sdfMinDistancePercent - See `SDF_DISTANCE_PERCENT`
* @property {number} sdfGlyphSize - The size of each glyph's SDF; see `configureTextBuilder`.
* @property {number} sdfExponent - The exponent used in encoding the SDF's values; see `configureTextBuilder`.
* @property {Float32Array} glyphBounds - List of [minX, minY, maxX, maxY] quad bounds for each glyph.

@@ -91,6 +88,8 @@ * @property {Float32Array} glyphAtlasIndices - List holding each glyph's index in the SDF atlas.

* @property {number} topBaseline - The y position of the top line's baseline.
* @property {Array<number>} totalBounds - The total [minX, minY, maxX, maxY] rect including all glyph
* quad bounds; this will be slightly larger than the actual glyph path edges due to SDF padding.
* @property {Array<number>} totalBlockSize - The [width, height] of the text block; this does not include
* extra SDF padding so it is accurate to use for measurement.
* @property {Array<number>} blockBounds - The total [minX, minY, maxX, maxY] rect of the whole text block;
* this can include extra vertical space beyond the visible glyphs due to lineHeight, and is
* equivalent to the dimensions of a block-level text element in CSS.
* @property {Array<number>} visibleBounds -
* @property {Array<number>} totalBounds - DEPRECATED; use blockBounds instead.
* @property {Array<number>} totalBlockSize - DEPRECATED; use blockBounds instead
* @property {Array<number>} chunkedBounds - List of bounding rects for each consecutive set of N glyphs,

@@ -115,2 +114,3 @@ * in the format `{start:N, end:N, rect:[minX, minY, maxX, maxY]}`.

function getTextRenderInfo(args, callback) {
hasRequested = true
args = assign({}, args)

@@ -145,3 +145,3 @@

// Init the atlas for this font if needed
const {textureWidth} = CONFIG
const {textureWidth, sdfExponent} = CONFIG
const {sdfGlyphSize} = args

@@ -203,3 +203,3 @@ let atlasKey = `${args.font}@${sdfGlyphSize}`

sdfGlyphSize,
sdfMinDistancePercent: SDF_DISTANCE_PERCENT,
sdfExponent,
glyphBounds: result.glyphBounds,

@@ -215,5 +215,14 @@ glyphAtlasIndices: result.glyphAtlasIndices,

topBaseline: result.topBaseline,
totalBounds: result.totalBounds,
totalBlockSize: result.totalBlockSize,
timings: result.timings
blockBounds: result.blockBounds,
visibleBounds: result.visibleBounds,
timings: result.timings,
get totalBounds() {
console.log('totalBounds deprecated, use blockBounds instead')
return result.blockBounds
},
get totalBlockSize() {
console.log('totalBlockSize deprecated, use blockBounds instead')
const [x0, y0, x1, y1] = result.blockBounds
return [x1 - x0, y1 - y0]
}
}))

@@ -271,3 +280,2 @@ })

CONFIG,
SDF_DISTANCE_PERCENT,
fontParser,

@@ -278,12 +286,6 @@ createGlyphSegmentsQuadtree,

],
init(config, sdfDistancePercent, fontParser, createGlyphSegmentsQuadtree, createSDFGenerator, createFontProcessor) {
const sdfGenerator = createSDFGenerator(
createGlyphSegmentsQuadtree,
{
sdfDistancePercent
}
)
return createFontProcessor(fontParser, sdfGenerator, {
defaultFontUrl: config.defaultFontURL
})
init(config, fontParser, createGlyphSegmentsQuadtree, createSDFGenerator, createFontProcessor) {
const {sdfExponent, sdfMargin, defaultFontURL} = config
const sdfGenerator = createSDFGenerator(createGlyphSegmentsQuadtree, { sdfExponent, sdfMargin })
return createFontProcessor(fontParser, sdfGenerator, { defaultFontURL })
}

@@ -290,0 +292,0 @@ })

import { createDerivedMaterial, voidMainRegExp } from 'troika-three-utils'
import { Vector2, Vector4, Matrix3 } from 'three'
import { Color, Vector2, Vector4, Matrix3 } from 'three'

@@ -12,8 +12,10 @@ // language=GLSL

uniform bool uTroikaUseGlyphColors;
uniform float uTroikaOutlineWidth;
attribute vec4 aTroikaGlyphBounds;
attribute float aTroikaGlyphIndex;
attribute vec3 aTroikaGlyphColor;
varying vec2 vTroikaSDFTextureUV;
varying float vTroikaGlyphIndex;
varying vec2 vTroikaGlyphUV;
varying vec3 vTroikaGlyphColor;
varying vec2 vTroikaGlyphDimensions;
`

@@ -24,24 +26,19 @@

vec4 bounds = aTroikaGlyphBounds;
vec4 outlineBounds = vec4(bounds.xy - uTroikaOutlineWidth, bounds.zw + uTroikaOutlineWidth);
vec4 clippedBounds = vec4(
clamp(bounds.xy, uTroikaClipRect.xy, uTroikaClipRect.zw),
clamp(bounds.zw, uTroikaClipRect.xy, uTroikaClipRect.zw)
clamp(outlineBounds.xy, uTroikaClipRect.xy, uTroikaClipRect.zw),
clamp(outlineBounds.zw, uTroikaClipRect.xy, uTroikaClipRect.zw)
);
vec2 clippedXY = (mix(clippedBounds.xy, clippedBounds.zw, position.xy) - bounds.xy) / (bounds.zw - bounds.xy);
vTroikaGlyphUV = clippedXY.xy;
float cols = uTroikaSDFTextureSize.x / uTroikaSDFGlyphSize;
vTroikaSDFTextureUV = vec2(
mod(aTroikaGlyphIndex, cols) + clippedXY.x,
floor(aTroikaGlyphIndex / cols) + clippedXY.y
) * uTroikaSDFGlyphSize / uTroikaSDFTextureSize;
position.xy = mix(bounds.xy, bounds.zw, clippedXY);
uv = vec2(
(position.x - uTroikaTotalBounds.x) / (uTroikaTotalBounds.z - uTroikaTotalBounds.x),
(position.y - uTroikaTotalBounds.y) / (uTroikaTotalBounds.w - uTroikaTotalBounds.y)
);
uv = (position.xy - uTroikaTotalBounds.xy) / (uTroikaTotalBounds.zw - uTroikaTotalBounds.xy);
position = uTroikaOrient * position;
normal = uTroikaOrient * normal;
vTroikaGlyphIndex = aTroikaGlyphIndex;
vTroikaGlyphUV = clippedXY.xy;
vTroikaGlyphDimensions = vec2(bounds[2] - bounds[0], bounds[3] - bounds[1]);
`

@@ -52,12 +49,72 @@

uniform sampler2D uTroikaSDFTexture;
uniform float uTroikaSDFMinDistancePct;
uniform vec2 uTroikaSDFTextureSize;
uniform float uTroikaSDFGlyphSize;
uniform float uTroikaSDFExponent;
uniform float uTroikaOutlineWidth;
uniform vec3 uTroikaOutlineColor;
uniform bool uTroikaSDFDebug;
varying vec2 vTroikaSDFTextureUV;
varying vec2 vTroikaGlyphUV;
varying float vTroikaGlyphIndex;
varying vec2 vTroikaGlyphDimensions;
float troikaGetTextAlpha() {
float troikaSDFValue = texture2D(uTroikaSDFTexture, vTroikaSDFTextureUV).r;
float troikaSdfValueToSignedDistance(float alpha) {
// Inverse of encoding in SDFGenerator.js
${''/* TODO - there's some slight inaccuracy here when dealing with interpolated alpha values; those
are linearly interpolated where the encoding is exponential. Look into improving this by rounding
to nearest 2 whole texels, decoding those exponential values, and linearly interpolating the result.
*/}
float maxDimension = max(vTroikaGlyphDimensions.x, vTroikaGlyphDimensions.y);
float absDist = (1.0 - pow(2.0 * (alpha > 0.5 ? 1.0 - alpha : alpha), 1.0 / uTroikaSDFExponent)) * maxDimension;
float signedDist = absDist * (alpha > 0.5 ? -1.0 : 1.0);
return signedDist;
}
float troikaGlyphUvToSdfValue(vec2 uv) {
float cols = uTroikaSDFTextureSize.x / uTroikaSDFGlyphSize;
vec2 textureUV = vec2(
mod(vTroikaGlyphIndex, cols) + uv.x,
floor(vTroikaGlyphIndex / cols) + uv.y
) * uTroikaSDFGlyphSize / uTroikaSDFTextureSize;
return texture2D(uTroikaSDFTexture, textureUV).r;
}
float troikaGlyphUvToDistance(vec2 uv) {
return troikaSdfValueToSignedDistance(troikaGlyphUvToSdfValue(uv));
}
vec4 troikaGetTextColor(float distanceOffset, vec4 bgColor, vec4 fgColor) {
vec2 clampedGlyphUV = clamp(vTroikaGlyphUV, 0.5 / uTroikaSDFGlyphSize, 1.0 - 0.5 / uTroikaSDFGlyphSize);
float distance = troikaGlyphUvToDistance(clampedGlyphUV);
if (clampedGlyphUV != vTroikaGlyphUV) {
// Naive extrapolated distance:
float distToUnclamped = length((vTroikaGlyphUV - clampedGlyphUV) * vTroikaGlyphDimensions);
distance += distToUnclamped;
${''/*
// TODO more refined extrapolated distance by adjusting for angle of gradient at edge...
// This has potential but currently gives very jagged extensions, maybe due to precision issues?
float uvStep = 1.0 / uTroikaSDFGlyphSize;
vec2 neighbor1UV = clampedGlyphUV + (
vTroikaGlyphUV.x != clampedGlyphUV.x ? vec2(0.0, uvStep * sign(0.5 - vTroikaGlyphUV.y)) :
vTroikaGlyphUV.y != clampedGlyphUV.y ? vec2(uvStep * sign(0.5 - vTroikaGlyphUV.x), 0.0) :
vec2(0.0)
);
vec2 neighbor2UV = clampedGlyphUV + (
vTroikaGlyphUV.x != clampedGlyphUV.x ? vec2(0.0, uvStep * -sign(0.5 - vTroikaGlyphUV.y)) :
vTroikaGlyphUV.y != clampedGlyphUV.y ? vec2(uvStep * -sign(0.5 - vTroikaGlyphUV.x), 0.0) :
vec2(0.0)
);
float neighbor1Distance = troikaGlyphUvToDistance(neighbor1UV);
float neighbor2Distance = troikaGlyphUvToDistance(neighbor2UV);
float distToUnclamped = length((vTroikaGlyphUV - clampedGlyphUV) * vTroikaGlyphDimensions);
float distToNeighbor = length((clampedGlyphUV - neighbor1UV) * vTroikaGlyphDimensions);
float gradientAngle1 = min(asin(abs(neighbor1Distance - distance) / distToNeighbor), PI / 2.0);
float gradientAngle2 = min(asin(abs(neighbor2Distance - distance) / distToNeighbor), PI / 2.0);
distance += (cos(gradientAngle1) + cos(gradientAngle2)) / 2.0 * distToUnclamped;
*/}
}
#if defined(IS_DEPTH_MATERIAL) || defined(IS_DISTANCE_MATERIAL)
float alpha = step(0.5, troikaSDFValue);
float alpha = 1.0 - step(distanceOffset, distance);
#else

@@ -67,25 +124,18 @@ ${''/*

on the potential change in the SDF's alpha from this fragment to its neighbor. This strategy maximizes
readability and edge crispness at all sizes and screen resolutions. Interestingly, this also means that
below a minimum size we're effectively displaying the SDF texture unmodified.
readability and edge crispness at all sizes and screen resolutions.
*/}
#if defined(GL_OES_standard_derivatives) || __VERSION__ >= 300
float aaDist = min(
0.5,
0.5 * min(
fwidth(vTroikaGlyphUV.x),
fwidth(vTroikaGlyphUV.y)
)
) / uTroikaSDFMinDistancePct;
float aaDist = length(fwidth(vTroikaGlyphUV * vTroikaGlyphDimensions)) * 0.5;
#else
float aaDist = 0.01;
float aaDist = vTroikaGlyphDimensions.x / 64.0;
#endif
float alpha = uTroikaSDFDebug ? troikaSDFValue : smoothstep(
0.5 - aaDist,
0.5 + aaDist,
troikaSDFValue
float alpha = smoothstep(
distanceOffset + aaDist,
distanceOffset - aaDist,
distance
);
#endif
return alpha;
return mix(bgColor, fgColor, alpha);
}

@@ -96,7 +146,18 @@ `

const FRAGMENT_TRANSFORM = `
float troikaAlphaMult = troikaGetTextAlpha();
if (troikaAlphaMult == 0.0) {
vec4 outlineColor = uTroikaOutlineWidth > 0.0
? troikaGetTextColor(uTroikaOutlineWidth, vec4(uTroikaOutlineColor, 0.0), vec4(uTroikaOutlineColor, 1.0))
: vec4(gl_FragColor.rgb, 0.0);
gl_FragColor = troikaGetTextColor(0.0, outlineColor, gl_FragColor);
// Shift depth of outlines back so they don't cover up neighboring glyphs
// TODO must make this work in Safari and other WebGL1 impls without the extension
#if defined(EXT_frag_depth) || __VERSION__ >= 300
gl_FragDepth = gl_FragCoord.z + (uTroikaOutlineWidth > 0.0 && gl_FragColor == outlineColor ? 1e-6 : 0.0);
#endif
// Debug raw SDF
gl_FragColor = uTroikaSDFDebug ? vec4(1.0, 1.0, 1.0, troikaGlyphUvToSdfValue(vTroikaGlyphUV)) : gl_FragColor;
if (gl_FragColor.a == 0.0) {
discard;
} else {
gl_FragColor.a *= troikaAlphaMult;
}

@@ -112,3 +173,6 @@ `

chained: true,
extensions: {derivatives: true},
extensions: {
derivatives: true,
fragDepth: true
},
uniforms: {

@@ -118,5 +182,7 @@ uTroikaSDFTexture: {value: null},

uTroikaSDFGlyphSize: {value: 0},
uTroikaSDFMinDistancePct: {value: 0},
uTroikaSDFExponent: {value: 0},
uTroikaTotalBounds: {value: new Vector4(0,0,0,0)},
uTroikaClipRect: {value: new Vector4(0,0,0,0)},
uTroikaOutlineWidth: {value: 0},
uTroikaOutlineColor: {value: new Color(0)},
uTroikaOrient: {value: new Matrix3()},

@@ -123,0 +189,0 @@ uTroikaUseGlyphColors: {value: true},

@@ -42,3 +42,3 @@ /**

const {
defaultFontUrl
defaultFontURL
} = config

@@ -82,5 +82,5 @@

const onError = err => {
console.error(`Failure loading font ${url}${url === defaultFontUrl ? '' : '; trying fallback'}`, err)
if (url !== defaultFontUrl) {
url = defaultFontUrl
console.error(`Failure loading font ${url}${url === defaultFontURL ? '' : '; trying fallback'}`, err)
if (url !== defaultFontURL) {
url = defaultFontURL
tryLoad()

@@ -121,3 +121,3 @@ }

function loadFont(fontUrl, callback) {
if (!fontUrl) fontUrl = defaultFontUrl
if (!fontUrl) fontUrl = defaultFontURL
let font = fonts[fontUrl]

@@ -147,3 +147,3 @@ if (font) {

function getSdfAtlas(fontUrl, sdfGlyphSize, callback) {
if (!fontUrl) fontUrl = defaultFontUrl
if (!fontUrl) fontUrl = defaultFontURL
let atlasKey = `${fontUrl}@${sdfGlyphSize}`

@@ -174,3 +174,3 @@ let atlas = fontAtlases[atlasKey]

text='',
font=defaultFontUrl,
font=defaultFontURL,
sdfGlyphSize=64,

@@ -218,3 +218,3 @@ fontSize=1,

let caretPositions = null
let totalBounds = null
let visibleBounds = null
let chunkedBounds = null

@@ -241,3 +241,3 @@ let maxLineWidth = 0

const halfLeading = (lineHeight - (ascender - descender) * fontSizeMult) / 2
const topBaseline = -(fontSize + halfLeading)
const topBaseline = -(ascender * fontSizeMult + halfLeading)
const caretHeight = Math.min(lineHeight, (ascender - descender) * fontSizeMult)

@@ -328,34 +328,34 @@ const caretBottomOffset = (ascender + descender) / 2 * fontSizeMult - caretHeight / 2

if (!metricsOnly) {
// Find overall position adjustments for anchoring
let anchorXOffset = 0
let anchorYOffset = 0
if (anchorX) {
if (typeof anchorX === 'number') {
anchorXOffset = -anchorX
}
else if (typeof anchorX === 'string') {
anchorXOffset = -maxLineWidth * (
anchorX === 'left' ? 0 :
anchorX === 'center' ? 0.5 :
anchorX === 'right' ? 1 :
parsePercent(anchorX)
)
}
// Find overall position adjustments for anchoring
let anchorXOffset = 0
let anchorYOffset = 0
if (anchorX) {
if (typeof anchorX === 'number') {
anchorXOffset = -anchorX
}
if (anchorY) {
if (typeof anchorY === 'number') {
anchorYOffset = -anchorY
}
else if (typeof anchorY === 'string') {
let height = lines.length * lineHeight
anchorYOffset = anchorY === 'top' ? 0 :
anchorY === 'top-baseline' ? -topBaseline :
anchorY === 'middle' ? height / 2 :
anchorY === 'bottom' ? height :
anchorY === 'bottom-baseline' ? height - halfLeading + descender * fontSizeMult :
parsePercent(anchorY) * height
}
else if (typeof anchorX === 'string') {
anchorXOffset = -maxLineWidth * (
anchorX === 'left' ? 0 :
anchorX === 'center' ? 0.5 :
anchorX === 'right' ? 1 :
parsePercent(anchorX)
)
}
}
if (anchorY) {
if (typeof anchorY === 'number') {
anchorYOffset = -anchorY
}
else if (typeof anchorY === 'string') {
let height = lines.length * lineHeight
anchorYOffset = anchorY === 'top' ? 0 :
anchorY === 'top-baseline' ? -topBaseline :
anchorY === 'middle' ? height / 2 :
anchorY === 'bottom' ? height :
anchorY === 'bottom-baseline' ? height - halfLeading + descender * fontSizeMult :
parsePercent(anchorY) * height
}
}
if (!metricsOnly) {
// Process each line, applying alignment offsets, adding each glyph to the atlas, and

@@ -365,3 +365,3 @@ // collecting all renderable glyphs into a single collection.

glyphAtlasIndices = new Float32Array(renderableGlyphCount)
totalBounds = [INF, INF, -INF, -INF]
visibleBounds = [INF, INF, -INF, -INF]
chunkedBounds = []

@@ -481,15 +481,21 @@ let lineYOffset = topBaseline

// Determine final glyph bounds and add them to the glyphBounds array
// Determine final glyph quad bounds and add them to the glyphBounds array
const bounds = glyphAtlasInfo.renderingBounds
const start = idx * 4
const x0 = glyphBounds[start] = glyphInfo.x + bounds[0] * fontSizeMult + anchorXOffset
const y0 = glyphBounds[start + 1] = lineYOffset + bounds[1] * fontSizeMult + anchorYOffset
const x1 = glyphBounds[start + 2] = glyphInfo.x + bounds[2] * fontSizeMult + anchorXOffset
const y1 = glyphBounds[start + 3] = lineYOffset + bounds[3] * fontSizeMult + anchorYOffset
const startIdx = idx * 4
const xStart = glyphInfo.x + anchorXOffset
const yStart = lineYOffset + anchorYOffset
glyphBounds[startIdx] = xStart + bounds[0] * fontSizeMult
glyphBounds[startIdx + 1] = yStart + bounds[1] * fontSizeMult
glyphBounds[startIdx + 2] = xStart + bounds[2] * fontSizeMult
glyphBounds[startIdx + 3] = yStart + bounds[3] * fontSizeMult
// Track total bounds
if (x0 < totalBounds[0]) totalBounds[0] = x0
if (y0 < totalBounds[1]) totalBounds[1] = y0
if (x1 > totalBounds[2]) totalBounds[2] = x1
if (y1 > totalBounds[3]) totalBounds[3] = y1
// Track total visible bounds
const visX0 = xStart + glyphObj.xMin * fontSizeMult
const visY0 = yStart + glyphObj.yMin * fontSizeMult
const visX1 = xStart + glyphObj.xMax * fontSizeMult
const visY1 = yStart + glyphObj.yMax * fontSizeMult
if (visX0 < visibleBounds[0]) visibleBounds[0] = visX0
if (visY0 < visibleBounds[1]) visibleBounds[1] = visY0
if (visX1 > visibleBounds[2]) visibleBounds[2] = visX1
if (visY1 > visibleBounds[3]) visibleBounds[3] = visY1

@@ -502,6 +508,7 @@ // Track bounding rects for each chunk of N glyphs

chunk.end++
if (x0 < chunk.rect[0]) chunk.rect[0] = x0
if (y0 < chunk.rect[1]) chunk.rect[1] = y0
if (x1 > chunk.rect[2]) chunk.rect[2] = x1
if (y1 > chunk.rect[3]) chunk.rect[3] = y1
const chunkRect = chunk.rect
if (visX0 < chunkRect[0]) chunkRect[0] = visX0
if (visY0 < chunkRect[1]) chunkRect[1] = visY0
if (visX1 > chunkRect[2]) chunkRect[2] = visX1
if (visY1 > chunkRect[3]) chunkRect[3] = visY1

@@ -545,4 +552,9 @@ // Add to atlas indices array

topBaseline, //y coordinate of the top line's baseline
totalBounds, //total rect including all glyphBounds; will be slightly larger than glyph edges due to SDF padding
totalBlockSize: [maxLineWidth, lines.length * lineHeight], //width and height of the text block; accurate for layout measurement
blockBounds: [ //bounds for the whole block of text, including vertical padding for lineHeight
anchorXOffset,
anchorYOffset - lines.length * lineHeight,
anchorXOffset + maxLineWidth,
anchorYOffset
],
visibleBounds, //total bounds of visible text paths, may be larger or smaller than totalBounds
newGlyphSDFs: newGlyphs, //if this request included any new SDFs for the atlas, they'll be included here

@@ -563,5 +575,6 @@ timings

process(args, (result) => {
const [x0, y0, x1, y1] = result.blockBounds
callback({
width: result.totalBlockSize[0],
height: result.totalBlockSize[1]
width: x1 - x0,
height: y1 - y0
})

@@ -568,0 +581,0 @@ }, {metricsOnly: true})

@@ -118,3 +118,3 @@ /**

* For a given x/y, search the quadtree for the closest line segment and return
* its signed distance.
* its signed distance. Negative = inside, positive = outside, zero = on edge
* @param x

@@ -149,4 +149,4 @@ * @param y

// Flip to negative distance if outside the poly
if (!isPointInPoly(x, y)) {
// Flip to negative distance if inside the poly
if (isPointInPoly(x, y)) {
closestDist = -closestDist

@@ -153,0 +153,0 @@ }

/**
* Initializes and returns a function to generate an SDF texture for a given glyph.
* @param {function} createGlyphSegmentsQuadtree - factory for a GlyphSegmentsQuadtree implementation.
* @param {number} config.sdfDistancePercent - see docs for SDF_DISTANCE_PERCENT in TextBuilder.js
* @param {number} config.sdfExponent
* @param {number} config.sdfMargin
*

@@ -9,5 +10,3 @@ * @return {function(Object): {renderingBounds: [minX, minY, maxX, maxY], textureData: Uint8Array}}

function createSDFGenerator(createGlyphSegmentsQuadtree, config) {
const {
sdfDistancePercent
} = config
const { sdfExponent, sdfMargin } = config

@@ -19,4 +18,2 @@ /**

const INF = Infinity
/**

@@ -60,20 +57,24 @@ * Find the point on a quadratic bezier curve at t where t is in the range [0, 1]

// Choose a maximum distance radius in font units, based on the glyph's max dimensions
const fontUnitsMaxDist = Math.max(glyphW, glyphH) * sdfDistancePercent
// Choose a maximum search distance radius in font units, based on the glyph's max dimensions
const fontUnitsMaxSearchDist = Math.max(glyphW, glyphH)
// Use that, extending to the texture edges, to find conversion ratios between texture units and font units
const fontUnitsPerXTexel = (glyphW + fontUnitsMaxDist * 2) / sdfSize
const fontUnitsPerYTexel = (glyphH + fontUnitsMaxDist * 2) / sdfSize
// Margin - add an extra 0.5 over the configured value because the outer 0.5 doesn't contain
// useful interpolated values and will be ignored anyway.
const fontUnitsMargin = Math.max(glyphW, glyphH) / sdfSize * (sdfMargin * sdfSize + 0.5)
const textureMinFontX = glyphObj.xMin - fontUnitsMaxDist - fontUnitsPerXTexel
const textureMinFontY = glyphObj.yMin - fontUnitsMaxDist - fontUnitsPerYTexel
const textureMaxFontX = glyphObj.xMax + fontUnitsMaxDist + fontUnitsPerXTexel
const textureMaxFontY = glyphObj.yMax + fontUnitsMaxDist + fontUnitsPerYTexel
// Metrics of the texture/quad in font units
const textureMinFontX = glyphObj.xMin - fontUnitsMargin
const textureMinFontY = glyphObj.yMin - fontUnitsMargin
const textureMaxFontX = glyphObj.xMax + fontUnitsMargin
const textureMaxFontY = glyphObj.yMax + fontUnitsMargin
const fontUnitsTextureWidth = textureMaxFontX - textureMinFontX
const fontUnitsTextureHeight = textureMaxFontY - textureMinFontY
const fontUnitsTextureMaxDim = Math.max(fontUnitsTextureWidth, fontUnitsTextureHeight)
function textureXToFontX(x) {
return textureMinFontX + (textureMaxFontX - textureMinFontX) * x / sdfSize
return textureMinFontX + fontUnitsTextureWidth * x / sdfSize
}
function textureYToFontY(y) {
return textureMinFontY + (textureMaxFontY - textureMinFontY) * y / sdfSize
return textureMinFontY + fontUnitsTextureHeight * y / sdfSize
}

@@ -144,7 +145,14 @@

textureYToFontY(sdfY + 0.5),
fontUnitsMaxDist
fontUnitsMaxSearchDist
)
//if (!isFinite(signedDist)) throw 'infinite distance!'
let alpha = isFinite(signedDist) ? Math.round(255 * (1 + signedDist / fontUnitsMaxDist) * 0.5) : signedDist
alpha = Math.max(0, Math.min(255, alpha)) //clamp
// Use an exponential scale to ensure the texels very near the glyph path have adequate
// precision, while allowing the distance field to cover the entire texture, given that
// there are only 8 bits available. Formula visualized: https://www.desmos.com/calculator/uiaq5aqiam
let alpha = Math.pow((1 - Math.abs(signedDist) / fontUnitsTextureMaxDim), sdfExponent) / 2
if (signedDist < 0) {
alpha = 1 - alpha
}
alpha = Math.max(0, Math.min(255, Math.round(alpha * 255))) //clamp
textureData[sdfY * sdfSize + sdfX] = alpha

@@ -151,0 +159,0 @@ }

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc