diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index abeb0b207d1..4581bc16819 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,7 +43,7 @@ jobs: size: { fabric: { minified: fs.statSync('${{ env.minified }}').size, bundled: fs.statSync('${{ env.bundled }}').size } } }); - name: checkout src files - run: git checkout origin/master -- src fabric.ts index.ts index.node.ts .browserslistrc + run: git checkout origin/master -- src fabric.ts index.ts index.node.ts .browserslistrc rollup.config.mjs - name: upstream build stats run: npm run build -- -s - name: persist diff --git a/CHANGELOG.md b/CHANGELOG.md index c1fdc1e6c37..1091cf545cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## [next] +- feat(): Export setFilterBackend and port the texture filtering option from fabric 5, exports some extra types [#8954](https://github.com/fabricjs/fabric.js/pull/8954) - chore(): swap commonly used string with constants [#8933](https://github.com/fabricjs/fabric.js/pull/8933) - chore(TS): Add more text types [#8941](https://github.com/fabricjs/fabric.js/pull/8941) - ci(): fix changelog action race condition [#8949](https://github.com/fabricjs/fabric.js/pull/8949) diff --git a/fabric.ts b/fabric.ts index a70879fdcab..1cdb11459aa 100644 --- a/fabric.ts +++ b/fabric.ts @@ -34,6 +34,7 @@ export { SprayBrush } from './src/brushes/SprayBrush'; export { PatternBrush } from './src/brushes/PatternBrush'; export { FabricObject as Object } from './src/shapes/Object/FabricObject'; +export type { TProps } from './src/shapes/Object/types'; export { Line } from './src/shapes/Line'; export { Circle } from './src/shapes/Circle'; export { Triangle } from './src/shapes/Triangle'; @@ -48,6 +49,11 @@ export { Textbox } from './src/shapes/Textbox'; export { Group } from './src/shapes/Group'; export { ActiveSelection } from './src/shapes/ActiveSelection'; export { Image } from './src/shapes/Image'; +export type { + ImageSource, + SerializedImageProps, + ImageProps, +} from './src/shapes/Image'; export { createCollectionMixin } from './src/Collection'; export * as util from './src/util'; diff --git a/rollup.config.mjs b/rollup.config.mjs index b156c1f0297..0a4bd3c1108 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -32,6 +32,15 @@ const plugins = [ * @param {*} warn */ function onwarn(warning, warn) { + // we error at any warning. + // we allow-list the errors we understand are not harmful + if ( + warning.code === 'PLUGIN_WARNING' && + !warning.message.includes('sourcemap') + ) { + console.error(chalk.redBright(warning.message)); + throw Object.assign(new Error(), warning); + } if (warning.code === 'CIRCULAR_DEPENDENCY') { console.error(chalk.redBright(warning.message)); throw Object.assign(new Error(), warning); diff --git a/src/canvas/StaticCanvas.ts b/src/canvas/StaticCanvas.ts index ef9378e7587..6fd42e11912 100644 --- a/src/canvas/StaticCanvas.ts +++ b/src/canvas/StaticCanvas.ts @@ -1319,17 +1319,16 @@ export class StaticCanvas< if (!isTextObject(obj)) { return; } - let fontFamily = obj.fontFamily; + const { styles, fontFamily } = obj; if (fontList[fontFamily] || !fontPaths[fontFamily]) { return; } fontList[fontFamily] = true; - if (!obj.styles) { + if (!styles) { return; } - Object.values(obj.styles).forEach((styleRow) => { - Object.values(styleRow).forEach((textCharStyle) => { - fontFamily = textCharStyle.fontFamily; + Object.values(styles).forEach((styleRow) => { + Object.values(styleRow).forEach(({ fontFamily = '' }) => { if (!fontList[fontFamily] && fontPaths[fontFamily]) { fontList[fontFamily] = true; } diff --git a/src/filters/BlendImage.ts b/src/filters/BlendImage.ts index 0c0a4bbce90..c4638f3a10d 100644 --- a/src/filters/BlendImage.ts +++ b/src/filters/BlendImage.ts @@ -74,7 +74,7 @@ export class BlendImage extends BaseFilter { applyToWebGL(options: TWebGLPipelineState) { const gl = options.context, texture = this.createTexture(options.filterBackend, this.image); - this.bindAdditionalTexture(gl, texture, gl.TEXTURE1); + this.bindAdditionalTexture(gl, texture!, gl.TEXTURE1); super.applyToWebGL(options); this.unbindAdditionalTexture(gl, gl.TEXTURE1); } diff --git a/src/filters/FilterBackend.ts b/src/filters/FilterBackend.ts index 30d8e29392d..c49d8f05464 100644 --- a/src/filters/FilterBackend.ts +++ b/src/filters/FilterBackend.ts @@ -32,3 +32,7 @@ export function getFilterBackend(strict = true): FilterBackend { } return filterBackend; } + +export function setFilterBackend(backend: FilterBackend) { + filterBackend = backend; +} diff --git a/src/filters/WebGLFilterBackend.ts b/src/filters/WebGLFilterBackend.ts index e7c73a04fc0..b2e2936b8c7 100644 --- a/src/filters/WebGLFilterBackend.ts +++ b/src/filters/WebGLFilterBackend.ts @@ -190,7 +190,7 @@ export class WebGLFilterBackend { width, height, !cachedTexture ? source : undefined - ), + )!, passes: filters.length, webgl: true, aPosition: this.aPosition, @@ -243,42 +243,58 @@ export class WebGLFilterBackend { * Accepts specific dimensions to initialize the texture to or a source image. * * @param {WebGLRenderingContext} gl The GL context to use for creating the texture. - * @param {Number} width The width to initialize the texture at. - * @param {Number} height The height to initialize the texture. - * @param {HTMLImageElement|HTMLCanvasElement} textureImageSource A source for the texture data. + * @param {number} width The width to initialize the texture at. + * @param {number} height The height to initialize the texture. + * @param {TexImageSource} textureImageSource A source for the texture data. + * @param {number} filter gl.NEAREST default or gl.LINEAR filters for the texture. + * This filter is very useful for LUTs filters. If you need interpolation use gl.LINEAR * @returns {WebGLTexture} */ createTexture( gl: WebGLRenderingContext, width: number, height: number, - textureImageSource?: TexImageSource + textureImageSource?: TexImageSource, + filter?: + | WebGLRenderingContextBase['NEAREST'] + | WebGLRenderingContextBase['LINEAR'] ) { + const { + NEAREST, + TEXTURE_2D, + RGBA, + UNSIGNED_BYTE, + CLAMP_TO_EDGE, + TEXTURE_MAG_FILTER, + TEXTURE_MIN_FILTER, + TEXTURE_WRAP_S, + TEXTURE_WRAP_T, + } = gl; const texture = gl.createTexture(); - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.bindTexture(TEXTURE_2D, texture); + gl.texParameteri(TEXTURE_2D, TEXTURE_MAG_FILTER, filter || NEAREST); + gl.texParameteri(TEXTURE_2D, TEXTURE_MIN_FILTER, filter || NEAREST); + gl.texParameteri(TEXTURE_2D, TEXTURE_WRAP_S, CLAMP_TO_EDGE); + gl.texParameteri(TEXTURE_2D, TEXTURE_WRAP_T, CLAMP_TO_EDGE); if (textureImageSource) { gl.texImage2D( - gl.TEXTURE_2D, + TEXTURE_2D, 0, - gl.RGBA, - gl.RGBA, - gl.UNSIGNED_BYTE, + RGBA, + RGBA, + UNSIGNED_BYTE, textureImageSource ); } else { gl.texImage2D( - gl.TEXTURE_2D, + TEXTURE_2D, 0, - gl.RGBA, + RGBA, width, height, 0, - gl.RGBA, - gl.UNSIGNED_BYTE, + RGBA, + UNSIGNED_BYTE, null ); } @@ -294,17 +310,27 @@ export class WebGLFilterBackend { * @param {HTMLImageElement|HTMLCanvasElement} textureImageSource A source to use to create the * texture cache entry if one does not already exist. */ - getCachedTexture(uniqueId: string, textureImageSource: TexImageSource) { - if (this.textureCache[uniqueId]) { - return this.textureCache[uniqueId]; + getCachedTexture( + uniqueId: string, + textureImageSource: TexImageSource, + filter?: + | WebGLRenderingContextBase['NEAREST'] + | WebGLRenderingContextBase['LINEAR'] + ): WebGLTexture | null { + const { textureCache } = this; + if (textureCache[uniqueId]) { + return textureCache[uniqueId]; } else { const texture = this.createTexture( this.gl, textureImageSource.width, textureImageSource.height, - textureImageSource + textureImageSource, + filter ); - this.textureCache[uniqueId] = texture; + if (texture) { + textureCache[uniqueId] = texture; + } return texture; } } diff --git a/src/filters/index.ts b/src/filters/index.ts index 0b6e703be0c..16c9d2858e4 100644 --- a/src/filters/index.ts +++ b/src/filters/index.ts @@ -1,6 +1,10 @@ export * as filters from './filters'; -export { getFilterBackend, initFilterBackend } from './FilterBackend'; +export { + getFilterBackend, + initFilterBackend, + setFilterBackend, +} from './FilterBackend'; export { Canvas2dFilterBackend } from './Canvas2dFilterBackend'; export { WebGLFilterBackend } from './WebGLFilterBackend'; export { isWebGLPipelineState } from './utils'; diff --git a/src/filters/typedefs.ts b/src/filters/typedefs.ts index 29139882235..bbf1afb4ff5 100644 --- a/src/filters/typedefs.ts +++ b/src/filters/typedefs.ts @@ -3,7 +3,7 @@ import type { Canvas2dFilterBackend } from './Canvas2dFilterBackend'; export type TProgramCache = any; -export type TTextureCache = any; +export type TTextureCache = Record; export type TPipelineResources = { blendImage?: HTMLCanvasElement; diff --git a/src/shapes/Image.ts b/src/shapes/Image.ts index f28e4414b53..0be259af707 100644 --- a/src/shapes/Image.ts +++ b/src/shapes/Image.ts @@ -189,8 +189,8 @@ export class Image< * @param {ImageSource | string} element Image element * @param {Object} [options] Options object */ - constructor(elementId: string, options: Props); - constructor(element: ImageSource, options: Props); + constructor(elementId: string, options?: Props); + constructor(element: ImageSource, options?: Props); constructor(arg0: ImageSource | string, options: Props = {} as Props) { super({ filters: [], ...options }); this.cacheKey = `texture${uid()}`; diff --git a/src/shapes/Object/Object.ts b/src/shapes/Object/Object.ts index 5c5238c1cf4..a3734788bf0 100644 --- a/src/shapes/Object/Object.ts +++ b/src/shapes/Object/Object.ts @@ -1299,11 +1299,11 @@ export class FabricObject< * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @returns {Promise} */ - clone(propertiesToInclude: string[]) { + clone(propertiesToInclude?: string[]): Promise { const objectForm = this.toObject(propertiesToInclude); return (this.constructor as typeof FabricObject).fromObject( objectForm - ) as unknown as this; + ) as unknown as Promise; } /** diff --git a/src/shapes/Text/StyledText.ts b/src/shapes/Text/StyledText.ts index 6c018cb8364..05ea813c390 100644 --- a/src/shapes/Text/StyledText.ts +++ b/src/shapes/Text/StyledText.ts @@ -6,25 +6,10 @@ import type { } from '../Object/types'; import { FabricObject } from '../Object/FabricObject'; import { styleProperties } from './constants'; +import type { StylePropertiesType } from './constants'; import type { Text } from './Text'; -export type TextStyleDeclaration = Partial< - Pick< - Text, - | 'fill' - | 'stroke' - | 'strokeWidth' - | 'fontSize' - | 'fontFamily' - | 'fontWeight' - | 'fontStyle' - | 'textBackgroundColor' - | 'deltaY' - | 'overline' - | 'underline' - | 'linethrough' - > ->; +export type TextStyleDeclaration = Partial>; export type TextStyle = { [line: number | string]: { [char: number | string]: TextStyleDeclaration }; @@ -38,7 +23,7 @@ export abstract class StyledText< declare abstract styles: TextStyle; protected declare abstract _textLines: string[][]; protected declare _forceClearCache: boolean; - static _styleProperties = styleProperties; + static _styleProperties: Readonly = styleProperties; abstract get2DCursorLocation( selectionStart: number, skipWrapping?: boolean @@ -78,8 +63,8 @@ export abstract class StyledText< * @param {Number} lineIndex to check the style on * @return {Boolean} */ - styleHas(property: string, lineIndex?: number): boolean { - if (!this.styles || !property || property === '') { + styleHas(property: keyof TextStyleDeclaration, lineIndex?: number): boolean { + if (!this.styles) { return false; } if (typeof lineIndex !== 'undefined' && !this.styles[lineIndex]) { @@ -111,8 +96,8 @@ export abstract class StyledText< * * @param {string} property The property to compare between characters and text. */ - cleanStyle(property: string) { - if (!this.styles || !property || property === '') { + cleanStyle(property: keyof TextStyleDeclaration) { + if (!this.styles) { return false; } const obj = this.styles; @@ -124,12 +109,8 @@ export abstract class StyledText< for (const p1 in obj) { letterCount = 0; for (const p2 in obj[p1]) { - const styleObject = obj[p1][p2], - // TODO: this shouldn't be necessary anymore with modern browsers - stylePropertyHasBeenSet = Object.prototype.hasOwnProperty.call( - styleObject, - property - ); + const styleObject = obj[p1][p2] || {}, + stylePropertyHasBeenSet = styleObject[property] !== undefined; stylesCount++; @@ -164,6 +145,7 @@ export abstract class StyledText< graphemeCount += this._textLines[i].length; } if (allStyleObjectPropertiesMatch && stylesCount === graphemeCount) { + // @ts-expect-error conspiracy theory of TS this[property as keyof this] = stylePropertyValue; this.removeStyle(property); } @@ -176,8 +158,8 @@ export abstract class StyledText< * * @param {String} props The property to remove from character styles. */ - removeStyle(property: string) { - if (!this.styles || !property || property === '') { + removeStyle(property: keyof TextStyleDeclaration) { + if (!this.styles) { return; } const obj = this.styles; @@ -289,6 +271,7 @@ export abstract class StyledText< styleProps = (this.constructor as typeof StyledText)._styleProperties; for (let i = 0; i < styleProps.length; i++) { const prop = styleProps[i]; + // @ts-expect-error TS complains even when we serve everything on a silver plate. styleObject[prop] = typeof style[prop] === 'undefined' ? this[prop as keyof this] diff --git a/src/shapes/Text/constants.ts b/src/shapes/Text/constants.ts index e4034c26e99..77d984eb6fd 100644 --- a/src/shapes/Text/constants.ts +++ b/src/shapes/Text/constants.ts @@ -35,7 +35,21 @@ export const additionalProps = [ 'direction', ] as const; -export const styleProperties = [ +export type StylePropertiesType = + | 'fill' + | 'stroke' + | 'strokeWidth' + | 'fontSize' + | 'fontFamily' + | 'fontWeight' + | 'fontStyle' + | 'textBackgroundColor' + | 'deltaY' + | 'overline' + | 'underline' + | 'linethrough'; + +export const styleProperties: Readonly = [ ...fontProperties, ...textDecorationProperties, 'stroke', diff --git a/src/util/misc/textStyles.ts b/src/util/misc/textStyles.ts index d4bb1140e6b..2ddd379b86b 100644 --- a/src/util/misc/textStyles.ts +++ b/src/util/misc/textStyles.ts @@ -102,10 +102,7 @@ export const stylesFromArray = ( return cloneDeep(styles); } const textLines = text.split('\n'), - stylesObject = {} as Record< - string | number, - Record> - >; + stylesObject: TextStyle = {}; let charIndex = -1, styleIndex = 0; //loop through each textLine