Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@lightningjs/blits

Package Overview
Dependencies
Maintainers
0
Versions
90
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@lightningjs/blits - npm Package Compare versions

Comparing version 1.7.1 to 1.8.0

11

CHANGELOG.md
# Changelog
## v1.8.0
_22 oct 2024_
- Refactored TypeScript definitions to give better autocompletion and general TS support for business logic
- Added option for custom characters in MSDF generated fonts
- Added export for symbols
- Upgraded renderer to 2.4.0
- Added transition progress event
## v1.7.1

@@ -4,0 +15,0 @@

54

docs/essentials/displaying_text.md

@@ -35,3 +35,37 @@ # Displaying Text

## Text dimensions
When you want to center your Text element, or properly position other Elements around your text, it is useful know the exact dimensions of your text.
Similar to the Image element (i.e. an Element with a `src`), Text elements also accept the `@loaded` attribute. This event is called, as soon as the text is rendered, and passes in the dimensions of the generated text texture.
The example below shows how to use the `@loaded`-attribute to position an Element as an underline, under a piece of text.
```js
export default Blits.Component('MyComponent', {
template: `
<Element>
<Text :content="$myText" @loaded="$textLoaded" />
<!-- gray underline -->
<Element :w="$w" h="2" y="$y" color="#333" />
</Element>
`,
props: ['myText'],
state() {
return {
w: 0,
y: 0,
}
},
methods: {
textLoaded(dimensions) {
console.log(`The text has a width of ${dimensions.w} and a height of ${dimensions.h}`)
// set the underline width to the exact width of the text
this.w = dimensions.w
// position the underline 8px below the text
this.y = dimensions.h + 8
}
}
```
## SDF and Canvas2d

@@ -53,3 +87,3 @@

Adding is custom font to a Blits App is quite straightforward. First, you'll need to place a `.ttf` version of your font in the `public` folder (i.e. `public/fonts/comic-sans.ttf`).
Adding is custom font to a Blits App is quite straightforward. First, you'll need to place a `.ttf`, `.woff` or `.otf` version of your font in the `public` folder (i.e. `public/fonts/comic-sans.ttf`).

@@ -78,1 +112,19 @@ Then you'll need to register the custom font in the Launch settings of your app (in `src/index.js`). The `fonts`-key in the settings is an `Array` that specifies all available fonts in your App.

### Custom characters
For MSDF font, a font atlas is created. By default this atlas includes all printable main ASCII characters. If you know before hand that you won't need certain characters, it would be more optimal to generate only those characters that are actually needed.
Similarly, if you need characters outside of the default set, like accents or other special characters, then these will need to be included in the generated font atlas.
In order to control which characters are generated for a specific font, you can add a _font config file_ per font inside the `assets/fonts` folder. Its name should match the name of the font file, like so: `myfont.config.json`.
The config file should look something like this:
```json
{
"charset": "0123456789éåaü"
}
```
With the configuration above, only numbers and the specified letters with accents will be generated (i.e. the default character set is overwritten).
> Please note that only special characters and accents that are part of the original font can be added to the generated font atlas. Other characters will show up as a `?`.

@@ -107,3 +107,3 @@ # Transitions

Sometimes you may want to perform an action when a transition ends or when it starts.
Sometimes you may want to perform an action when a transition _ends_ or when it _starts_.

@@ -132,1 +132,21 @@ You can easily hook into these transition events by adding a `start` and `end` key to the transition object. Both values should be a function (or a reference to a `method` on your component).

It is also possible to keep track of the entire progress of a transition. Every frametick during a transition (ideally once every 1.6ms), the renderer reports the progress of the transition. You can hook into this event by specifying a `progress` key on the transition configuration object, with a function to excute.
This function is executed _every_ frametick, and receives the current progress and the previous progress as its arguments. The progress is indicated as a value between `0` and `1`, where 0 means start and 1 means finished.
```js
export default Blits.Component('Gold', {
template: `
<Element color="gold" w="100" h="100"
:x.transition="{value: $x, progress: $transitionProgress}"
/>
`,
///
methods: {
transitionprogress(progress, previousProgress) {
if(progress >= 0.5 && previousProgress < 0.5) {
// halfway through the transition
}
},
}
```

@@ -18,17 +18,822 @@ /*

import Component from './src/component.js'
import Application from './src/application.js'
import Launch from './src/launch.js'
import Plugin from './src/plugin.js'
import {type ShaderEffect as RendererShaderEffect, type WebGlCoreShader} from '@lightningjs/renderer'
/**
* Blits - The Lightning 3 App Development Framework
*/
declare module Blits {
export { Component }
export { Application }
export { Launch }
export { Plugin }
declare module '@lightningjs/blits' {
interface Hooks {
/**
* Fires when the Component is being instantiated
* At this moment child elements will not be available yet
*/
init?: () => void;
/**
* Fires when the Component is fully initialized and ready for interaction.
*/
ready?: () => void;
/**
* Triggers when the Component receives focus.
*
* This event can fire multiple times during the component's lifecycle
*/
focus?: () => void;
/**
* Triggers when the Component loses focus.
*
* This event can fire multiple times during the component's lifecycle
*/
unfocus?: () => void;
/**
* Fires when the Component is being destroyed and removed.
*/
destroy?: () => void;
/**
* Fires upon each frame start (allowing you to tap directly into the renderloop)
*
* Note: This hook will fire continuously, multiple times per second!
*/
frameTick?: () => void;
/**
* Fires when the component enters the viewport _margin_ and is attached to the render tree
*
* This event can fire multiple times during the component's lifecycle
*/
attach?: () => void;
/**
* Fires when the component leaves the viewport _margin_ and is detached from the render tree
*
* This event can fire multiple times during the component's lifecycle
*/
detach?: () => void;
/**
* Fires when the component enters the visible viewport
*
* This event can fire multiple times during the component's lifecycle
*/
enter?: () => void;
/**
* Fires when the component leaves the visible viewport
*
* This event can fire multiple times during the component's lifecycle
*/
exit?: () => void;
}
interface Input {
[key: string]: (event: KeyboardEvent) => void | undefined,
/**
* Catch all input function
*
* Will be invoked when there is no dedicated function for a certain key
*/
// @ts-ignore
any?: (event: KeyboardEvent) => void
}
interface Log {
/**
* Log an info message
*/
info(...args): typeof console.info
/**
* Log an error message
*/
error(...args): typeof console.error
/**
* Log a warning
*/
warn(...args): typeof console.warn
/**
* Log a debug message
*/
debug(...args): typeof console.debug
}
interface RouteData {
[key: string]: any
}
// todo: specify valid route options
interface RouteOptions {
[key: string]: any
}
interface Router {
/**
* Navigate to a different location
*
* @param {string}
*/
to(location: string, data?: RouteData, options?: RouteOptions): void;
/**
* Navigate to the previous location
*/
back(): boolean;
/**
* Get the current route read-only
*/
readonly currentRoute: Route;
/**
* Get the list of all routes
*/
readonly routes: Route[];
/**
* Get navigating state
*/
readonly navigating: boolean;
}
type ComponentBase = {
/**
* Listen to events emitted by other components
*/
$listen: {
(event: string, callback: (args: any) => void, priority?: number): void;
}
/**
* Emit events that other components can listen to
* @param name - name of the event to be emitted
* @param data - optional data to be passed along
*/
$emit(name: string, data?: any): void;
/**
* Set a timeout that is automatically cleaned upon component destroy
*/
$setTimeout: (callback: (args: any) => void, ms?: number | undefined) => ReturnType<typeof setTimeout>
/**
* Clear a timeout
*/
$clearTimeout: (id: ReturnType<typeof setTimeout>) => void
/**
* Set an interval that is automatically cleaned upon component destroy
*/
$setInterval: (callback: (args: any) => void, ms?: number | undefined) => ReturnType<typeof setInterval>
/**
* Clear a interval
*/
$clearInterval: (id: ReturnType<typeof setInterval>) => void
/**
* Log to the console with prettier output and configurable debug levels in Settings
*/
$log: Log
/**
* Set focus to the Component, optionally pass a KeyboardEvent for instant event bubbling
*/
$focus: (event?: KeyboardEvent) => void
/**
* @deprecated
* Deprecated: use `this.$focus()` instead
*/
focus: (event?: KeyboardEvent) => void
/**
* Select a child Element or Component by ref
*
* Elements and Components in the template can have an optional ref argument.
* Returns an Element Instance or Component Instance.
* Useful for passing on the focus to a Child component.
*
* @example
* ```js
* const menu = this.$select('Menu')
* if(menu) {
* menu.$focus()
* }
* ```
*/
$select: (ref: string) => ComponentBase
/**
* @deprecated
* Deprecated: use `this.$select()` instead
*/
select: (ref: string) => ComponentBase
/**
* Announcer methods for screen reader support
*/
// $announcer: Announcer
/**
* Triggers a forced update on state variables.
*/
$trigger: (key: string) => void
/**
* @deprecated
*
* Triggers a forced update on state variables.
* Deprecated: use `this.$trigger()` instead
*/
trigger: (key: string) => void
/**
* Router instance
*/
$router: Router
}
/**
* Prop object
*/
type PropObject = {
/**
* Name of the prop
*/
key: string,
/**
* Whether the prop is required to be passed
*/
required?: boolean,
/**
* Default value for the prop when omited
*/
default?: any,
/**
* Cast the value of the prop
*
* @example
* ```js
* {
* cast: Number, // casts to a number
* cast: (v) => v.toUpperCase() // casts to uppercase
* }
* ```
*/
cast?: () => any
};
// Props Array
type Props = (string | PropObject)[];
// Extract the prop names from the props array
type ExtractPropNames<P extends Props> = {
readonly [K in P[number] as K extends string ? K : K extends { key: infer Key } ? Key : never]: any;
};
// Update the PropsDefinition to handle props as strings or objects
type PropsDefinition<P extends Props> = ExtractPropNames<P>;
type ComponentContext<P extends Props, S, M, C> = ThisType<PropsDefinition<P> & S & M & C & ComponentBase>
interface ComponentConfig<P extends Props, S, M, C, W> {
components?: {
[key: string]: ComponentFactory,
},
/**
* XML-based template string of the Component
*
* @example
* ```xml
* <Element :x="$x" w="400" h="1080" color="#64748b">
* <Element x="50" y="40">
* <Button color="#e4e4e7" />
* <Button color="#e4e4e7" y="100" />
* <Button color="#e4e4e7" y="200" />
* <Button color="#e4e4e7" y="300" />
* </Element>
* </Element>
* ```
*/
template?: String,
/**
* Allowed props to be passed into the Component by the parent
*
* Can be a simple array with `prop` keys as strings.
* Alternatively objects can be used to specify `required` props and `default` values
*
* @example
* ```js
* props: ['index', {
* key: 'alpha',
* required: true
* }, {
* key: 'color',
* default: 'red'
* }]
* ```
*/
props?: P;
/**
* Reactive internal state of the Component instance
*
* Should return an object (literal) with key value pairs.
* Can contain nested objects, but beware that too deep nesting can have
* a negative impact on performance
*
* @example
* ```js
* state() {
* return {
* items: [],
* color: 'red',
* alpha: 0.1
* }
* }
* ```
*/
state?: (this: PropsDefinition<P>) => S;
/**
* Methods for abstracting more complex business logic into separate function
*/
methods?: M & ComponentContext<P, S, M, C>
/**
* Hooking into Lifecycle events
*/
hooks?: Hooks & ComponentContext<P, S, M, C>
/**
* Tapping into user input
*/
input?: Input & ComponentContext<P, S, M, C>
/**
* Computed properties
*/
computed?: C & ComponentContext<P, S, M, C>
/**
* Watchers for changes to state variables, props or computed properties
*/
watch?: W & ComponentContext<P, S, M, C>
}
interface ApplicationConfig<P extends Props, S, M, C, W> extends ComponentConfig<P, S, M, C, W> {
/**
* Routes definition
*
* @example
*
* ```js
* routes: [
* { path: '/', component: Home },
* { path: '/details', component: Details },
* { path: '/account', component: Account },
* ]
* ```
*/
routes?: Route[]
}
interface Transition {
/**
* Name of the prop to transition (i.e. 'x', 'y', 'alpha', 'color')
*/
prop: string,
/**
* Value the prop should transition to (i.e. 0, 100, '#223388')
*/
value: any,
/**
* Duration of the transition in milliseconds, defaults to 300
*/
duration?: number,
/**
* Easing function to apply to the transition
*/
easing?: string,
/**
* Delay before the transition starts in milliseconds
*/
delay?: number
}
interface Before {
/**
* Name of the prop to set before the transition starts
*/
prop: string,
/**
* Value the prop to set before the transition starts
*/
value: any,
}
interface RouteTransition {
/**
* Setting or Array of Settings before new view enters into the router view
*/
before: Before | Before[],
/**
* Transition or Array of Transitions for new view to enters into the router view
*/
in: Transition | Transition[],
/**
* Transition or Array of Transitions for old view to leave the router view
*/
out: Transition | Transition[],
}
type RouteTransitionFunction = (previousRoute: Route, currentRoute: Route) => RequireAtLeastOne<RouteTransition>
interface RouteAnnounce {
/**
* Message to be announced
*/
message: String,
/**
* Politeness level
*
* Defaults to 'off'
*/
politeness?: 'off' | 'polite' | 'assertive'
}
type RequireAtLeastOne<T> = {
[K in keyof T]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<keyof T, K>>>
}[keyof T]
interface RouteHooks {
before?: (to: Route, from: Route) => string | Route;
}
type Route = {
/**
* URI path for the route
*/
path: string,
/**
* Component to load when activating the route
*/
component: ComponentFactory // todo: or promise returning a component instance
/**
* Transition configuration for the route
*/
transition?: RequireAtLeastOne<RouteTransition> | RouteTransitionFunction,
/**
* Extra route options
*/
options?: object // todo: specify which options are available,
/**
* Message to be announced when visiting the route (often used for accessibility purposes)
*
* Can be either a `String` with the message or an object that defines the message and the
* politeness level
*/
announce?: String | RouteAnnounce
/**
* Register hooks for the route
*/
hooks?: RouteHooks
/**
* Route path parameters
*/
readonly params?: {
[key: string]: string | number
}
/**
* Allows for attaching custom data to a route, either hardcoded in the
* route definition or asigned to the route object in a before hook
*
* Will be merged with the route params and navigation data and passed as
* props into the route component
*/
data?: {
[key: string]: any
}
}
type ComponentFactory = () => void
// Launch Related
type DebugLevel = 0 | 1 | 2
type LogTypes = 'info' | 'warn' | 'error' | 'debug'
interface WebFont {
/**
* Name of the font family
*/
family: string,
/**
* Type of font (web)
*/
type: 'web',
/**
* Location to the font file (i.e. `/fonts/OpenSans-Medium.ttf`)
*/
file: string
}
interface SdfFontWithFile {
/**
* Location of the font file (i.e. `/fonts/OpenSans-Medium.ttf`)
*/
file: string
}
interface SdfFontWithPngJson {
/**
* Location of the font map (i.e. `'/fonts/Lato-Regular.msdf.json'`)
*/
json: string,
/**
* Location of the font png (i.e. `'/fonts/Lato-Regular.msdf.png'`)
*/
png: string
}
type SdfFont = {
/**
* Name of the font family
*/
family: string,
/**
* Type of font (web)
*/
type: 'msdf' | 'sdf',
} & (SdfFontWithFile | SdfFontWithPngJson)
type Font = WebFont | SdfFont
type ShaderEffect = {
name: string,
type: RendererShaderEffect
}
type Shader = {
name: string,
type: WebGlCoreShader
}
type ScreenResolutions = 'hd' | '720p' | 720 | 'fhd' | 'fullhd' | '1080p' | 1080 | '4k' | '2160p' | 2160
type ReactivityModes = 'Proxy' | 'defineProperty'
type RenderModes = 'webgl' | 'canvas'
/**
* Settings
*
* Launcher function that sets up the Lightning renderer and instantiates
* the Blits App
*/
interface Settings {
/**
* Width of the Application
*/
w?: number,
/**
* Height of the Application
*/
h?: number,
/**
* Whether to enable multithreaded
*/
multithreaded?: boolean,
/**
* Debug level for console log messages
*/
debugLevel?: DebugLevel | LogTypes[],
/**
* Fonts to be used in the Application
*/
fonts?: Font[],
/**
* Effects to be used by DynamicShader
*/
effects?: ShaderEffect[],
/**
* Shaders to be used in the application
*/
shaders?: Shader[],
/**
* Default font family to use in the Application when no font attribute is specified
* on a Text-component
*
* The default font must be registered in the `fonts` array in the settings.
*
* Defaults to `sans-serif` font family, which is the default of the Lightning Renderer
*/
defaultFont?: string,
/**
* Custom keymapping
*/
keymap?: object,
/**
* Mode of reactivity (`Proxy` or `defineProperty`)
*/
reactivityMode?: ReactivityModes,
/**
* Screen resolution of the device, defining the pixelRatio used to convert dimensions
* and positions in the App code to the actual device logical coordinates
*
* If not supplied, Blits will try to autodetect the device screen resolution. Otherwise
* the exact dimensions and positions used the app code are used.
*
* Note: If the option `pixelRatio` is specified in the Settings object, this value will take presedence
* over the screen resolution setting.
*
* Currently 3 screen resolutions are supported, which can be defined with different alias values:
*
* For 720x1080 (1px = 0.66666667px)
* - hd
* - 720p
* - 720
*
* For 1080x1920 (1px = 1px)
* - fhd
* - fullhd
* - 1080p
* - 1080
*
* For 2160x3840 (1px = 2px)
* - 4k
* - 2160p
* - 2160
*/
screenResolution?: ScreenResolutions,
/**
* Custom pixel ratio of the device used to convert dimensions
* and positions in the App code to the actual device logical coordinates
*
* Takes presedence over the `screenResolution` setting
*
* Defaults to 1 if not specified
*/
pixelRatio?: number
/**
* Interval in milliseconds to receive FPS updates
*
* @remarks
* If set to `0`, FPS updates will be disabled.
*
* @defaultValue `1000` (disabled)
*/
fpsInterval?: number
/**
* Maximum number of web workers to spin up simultaneously for offloading functionality such
* as image loading to separate threads (when supported by the browser)
*
* If not specified defaults to the number of logical processers available as reported by
* `navigator.hardwareConcurrency` (or 2 if `navigator.hardwareConcurrency` is not supported)
*/
webWorkersLimit?: number
/**
* Background color of the canvas (also known as the clearColor)
*
* Can be a color name (red, blue, silver), a hexadecimal color (`#000000`, `#ccc`),
* or a color number in rgba order (`0xff0033ff`)
*
* Defauls to transparent (`0x00000000`)
*
*/
canvasColor?: string,
/**
* Enable inspector
*
* Enables the inspector tool for debugging and inspecting the application, the node tree
* will be replicated in the DOM and can be inspected using the browser's developer tools
*
* Defaults to `false`
*/
inspector?: boolean,
/**
* Add an extra margin to the viewport for earlier pre-loading of elements and components
*
* By default the Lightning renderer, only renders elements that are inside the defined viewport.
* Everything outside of these bounds is removed from the render tree.
*
* With the viewportMargin you have the option to _virtually_ increase the viewport area,
* to expedite the pre-loading of elements and / or delay the unloading of elements depending
* on their position in the (virtual) viewport
*
* The margin can be specified in 4 directions by defining an array [top, right, bottom, left],
* or as a single number which is then applied to all 4 directions equally.
*
* Defaults to `0`
*/
viewportMargin?: number | [number, number, number, number],
/**
* Threshold in `Megabytes` after which all the textures that are currently not visible
* within the configured viewport margin will be be freed and cleaned up
*
* When passed `0` the threshold is disabled and textures will not be actively freed
* and cleaned up
*
* Defaults to `200` (mb)
*/
gpuMemoryLimit?: number,
/**
* Defines which mode the renderer should operate in: `webgl` or `canvas`
*
* SDF fonts are not supported in _canvas_ renderMode. Instead, _web_ fonts should
* be used. Also note that Canvas2d rendering doesnt support the use of shaders.
*
* Defaults to `webgl`
*/
renderMode?: RenderModes,
/**
* The time, in milliseconds, after which Blits considers a key press a _hold_ key press
*
* During a hold key press the focus delegation behaviour is different: when scrolling
* through a long list, focus is not handed over to each individual list item, creating a
* smoother experience
*
* Defaults to `50` (ms)
*/
holdTimeout?: number
}
interface State {
[key: string]: any
}
interface Methods {
[key: string]: any
}
interface Watch {
[key: string]: any
}
interface Computed {
[key: string]: any
}
interface Plugin {
/**
* Name of the plugin. The plugin will be accessible on each Component's
* `this` scope under this name, prefixed with a `$` (i.e. myplugin => `this.$myplugin`)
*/
name: string,
/**
* Singleton function that will be used to instantiate the plugin.
* Should do all necessary setup and ideally return an object with
* properties or methods that can be used in the App
*/
plugin: () => any
}
/**
* Blits App Framework
*/
export interface Blits {
/**
* Blits Application
* @param {ApplicationConfig} config - Application Configuration object
*/
Application<
P extends Props,
S extends State,
M extends Methods,
C extends Computed,
W extends Watch>(config: ApplicationConfig<P, S, M, C, W>) : ComponentFactory
/**
* Blits Component
* @param {string} name - The name of the Component
* @param {ComponentConfig} config - Component Configuration object
*/
Component<
P extends Props,
S extends State,
M extends Methods,
C extends Computed,
W extends Watch>(name: string, config: ComponentConfig<P, S, M, C, W>) : ComponentFactory
/**
* Blits Launch
*/
Launch(App: ComponentFactory, target: HTMLElement | String, settings?: Settings) : void
/**
* Blits Plugin
*
* @param plugin
* Plugin object or singleton function that will be used to instantiate the
* plugin. Should do all necessary setup and ideally return an object with
* properties or methods that can be used in the App
*
* @param nameOrOptions
* Name of the plugin or options object, to be passed into the plugin instantiation.
*
* When passing a string, the value will be used as **name** of the plugin. The plugin
* will be accessible on each Component's `this` scope under this name, prefixed with a `$`
* (i.e. myplugin => `this.$myplugin`)
*
* Passing a name is **required** when `plugin` argument is a singleton function
*
* When passing an object, the value will be considered to be an `options` object.
* The options object will be passed into the plugin instantiation method. Can
* be used to set default values
*
* @param options
* Object with options to be passed into the plugin instantiation method.
* Can be used to set default values
*/
Plugin(plugin: Plugin, nameOrOptions?: string | object , options?: object) : void
}
const Blits: Blits;
export default Blits;
}
export default Blits

7

package.json
{
"name": "@lightningjs/blits",
"version": "1.7.1",
"version": "1.8.0",
"description": "Blits: The Lightning 3 App Development Framework",

@@ -22,2 +22,3 @@ "bin": "bin/index.js",

},
"types": "./index.d.ts",
"lint-staged": {

@@ -51,4 +52,4 @@ "*.js": [

"dependencies": {
"@lightningjs/msdf-generator": "^1.0.2",
"@lightningjs/renderer": "^2.3.2"
"@lightningjs/msdf-generator": "^1.1.0",
"@lightningjs/renderer": "^2.4.0"
},

@@ -55,0 +56,0 @@ "repository": {

@@ -389,3 +389,3 @@ /*

if (transition.start && typeof transition.start === 'function') {
if (transition.start !== undefined && typeof transition.start === 'function') {
// fire transition start callback when animation really starts (depending on specified delay)

@@ -397,2 +397,10 @@ f.once('animating', () => {

if (transition.progress !== undefined && typeof transition.progress === 'function') {
let prevProgress = 0
f.on('tick', (_node, { progress }) => {
transition.progress.call(this.component, this, prop, progress, prevProgress)
prevProgress = progress
})
}
f.once('stopped', () => {

@@ -399,0 +407,0 @@ // remove the prop from scheduled transitions

import path from 'path'
import * as fs from 'fs'
import { createHash } from 'crypto'
import { genFont, setGeneratePaths } from '@lightningjs/msdf-generator'

@@ -24,2 +25,4 @@ import { adjustFont } from '@lightningjs/msdf-generator/adjustFont'

let config
let checksumPaths = []
let checksumData = []

@@ -57,2 +60,3 @@ export default function () {

let fontFile = null
let fontType = null
for (const fontExt of supportedExtensions) {

@@ -62,2 +66,3 @@ const potentialPath = path.join(fontDir, `${fontName}.${fontExt}`)

fontFile = potentialPath
fontType = '.' + fontExt
break

@@ -76,6 +81,9 @@ }

await fontGenerationQueue.enqueue(async () => {
if (!fs.existsSync(generatedFontFile)) {
// Check if generation is needed
if (isGenerationRequired(fontDir, targetDir, fontName, fontType)) {
const configFilePath = path.join(fontDir, `${fontName}.config.json`)
console.log(`\nGenerating ${targetDir}/${fontName}.msdf.${ext}`)
await generateSDF(fontFile, path.join(targetDir))
await generateSDF(fontFile, path.join(targetDir), configFilePath)
saveChecksum()
}

@@ -116,10 +124,13 @@ })

const relativePath = path.relative(publicDir, path.dirname(fontFile))
const fontExt = path.extname(fontFile)
const baseName = path.basename(fontFile).replace(/\.(ttf|otf|woff)$/i, '')
const msdfJsonPath = path.join(msdfOutputDir, relativePath, `${baseName}.msdf.json`)
const msdfPngPath = path.join(msdfOutputDir, relativePath, `${baseName}.msdf.png`)
const targetDir = path.join(msdfOutputDir, relativePath)
// Check if MSDF files are generated, if not, generate them
if (!fs.existsSync(msdfJsonPath) || !fs.existsSync(msdfPngPath)) {
// Check MSDF generation is required
if (isGenerationRequired(path.dirname(fontFile), targetDir, baseName, fontExt)) {
const configFilePath = path.join(path.dirname(fontFile), `${baseName}.config.json`)
console.log(`Generating missing MSDF files for ${fontFile}`)
await generateSDF(fontFile, path.join(msdfOutputDir, relativePath))
await generateSDF(fontFile, path.join(msdfOutputDir, relativePath), configFilePath)
saveChecksum()
}

@@ -135,7 +146,7 @@ }

const generateSDF = async (inputFilePath, outputDirPath) => {
const generateSDF = async (inputFilePath, outputDirPath, configFilePath) => {
// Ensure the destination directory exists
fs.mkdirSync(outputDirPath, { recursive: true })
setGeneratePaths(path.dirname(inputFilePath), outputDirPath)
setGeneratePaths(path.dirname(inputFilePath), outputDirPath, configFilePath)

@@ -177,4 +188,72 @@ let font = await genFont(path.basename(inputFilePath), 'msdf')

// Should not copy .checksum files to dist directory
if (srcPath.indexOf('.checksum') !== -1) continue
entry.isDirectory() ? copyDir(srcPath, destPath) : fs.copyFileSync(srcPath, destPath) // Copy files
}
}
// Check font config.json or font file modified since last generation
const isGenerationRequired = (fontDir, targetDir, fontName, fontExt) => {
const fontFilePath = path.resolve(fontDir, fontName + fontExt)
const fontChecksumPath = path.resolve(targetDir, fontName + fontExt + '.checksum')
const configJsonPath = path.resolve(fontDir, fontName + '.config.json')
const configChecksumPath = path.resolve(targetDir, fontName + '.config.checksum')
// If font file checksum not exists, should generate msdf
if (!fs.existsSync(fontChecksumPath)) {
checksumPaths.push(fontChecksumPath)
checksumData.push(generateHash(fontFilePath))
if (fs.existsSync(configJsonPath)) {
checksumPaths.push(configChecksumPath)
checksumData.push(generateHash(configJsonPath))
}
return true
}
// If config.json exists, can be newly added or modified since last generation of msdf
if (fs.existsSync(configJsonPath)) {
// If config.json checksum not exists, should generate msdf
if (!fs.existsSync(configChecksumPath)) {
checksumPaths.push(configChecksumPath)
checksumData.push(generateHash(configJsonPath))
return true
}
const isConfigModified = isInputFileModified(configJsonPath, configChecksumPath)
// If config.json is modified, should generate msdf
if (isConfigModified) return true
}
// Check font file modified, if modified, should generate msdf
return isInputFileModified(fontFilePath, fontChecksumPath)
}
const isInputFileModified = (inputFilePath, targetFilePath) => {
const inputChecksum = generateHash(inputFilePath)
const targetChecksum = fs.readFileSync(targetFilePath).toString()
if (inputChecksum !== targetChecksum) {
checksumPaths.push(targetFilePath)
checksumData.push(inputChecksum)
return true
}
return false
}
const generateHash = (filePath) => {
const buffer = fs.readFileSync(filePath)
return createHash('md5').update(buffer).digest('hex')
}
const saveChecksum = () => {
if (checksumPaths.length > 0 && checksumData.length > 0) {
for (let i = 0; i < checksumPaths.length; i++) {
// Save checksum
fs.writeFileSync(checksumPaths[i], checksumData[i])
}
checksumData.length = 0
checksumPaths.length = 0
}
}
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