By design htmlElementTexture is pretty tiny as it is use to not have maths classes and such being bundled with it.

In your case you could maybe rely on a dynamicTexture which also wraps a canvas or you need to override the getTextureMatrix function like the texture class does:

```
public getTextureMatrix(uBase = 1): Matrix {
if (
this.uOffset === this._cachedUOffset &&
this.vOffset === this._cachedVOffset &&
this.uScale * uBase === this._cachedUScale &&
this.vScale === this._cachedVScale &&
this.uAng === this._cachedUAng &&
this.vAng === this._cachedVAng &&
this.wAng === this._cachedWAng &&
this.uRotationCenter === this._cachedURotationCenter &&
this.vRotationCenter === this._cachedVRotationCenter &&
this.wRotationCenter === this._cachedWRotationCenter &&
this.homogeneousRotationInUVTransform === this._cachedHomogeneousRotationInUVTransform) {
return this._cachedTextureMatrix!;
}
this._cachedUOffset = this.uOffset;
this._cachedVOffset = this.vOffset;
this._cachedUScale = this.uScale * uBase;
this._cachedVScale = this.vScale;
this._cachedUAng = this.uAng;
this._cachedVAng = this.vAng;
this._cachedWAng = this.wAng;
this._cachedURotationCenter = this.uRotationCenter;
this._cachedVRotationCenter = this.vRotationCenter;
this._cachedWRotationCenter = this.wRotationCenter;
this._cachedHomogeneousRotationInUVTransform = this.homogeneousRotationInUVTransform;
if (!this._cachedTextureMatrix || !this._rowGenerationMatrix) {
this._cachedTextureMatrix = Matrix.Zero();
this._rowGenerationMatrix = new Matrix();
this._t0 = Vector3.Zero();
this._t1 = Vector3.Zero();
this._t2 = Vector3.Zero();
}
Matrix.RotationYawPitchRollToRef(this.vAng, this.uAng, this.wAng, this._rowGenerationMatrix!);
if (this.homogeneousRotationInUVTransform) {
Matrix.TranslationToRef(-this._cachedURotationCenter, -this._cachedVRotationCenter, -this._cachedWRotationCenter, TmpVectors.Matrix[0]);
Matrix.TranslationToRef(this._cachedURotationCenter, this._cachedVRotationCenter, this._cachedWRotationCenter, TmpVectors.Matrix[1]);
Matrix.ScalingToRef(this._cachedUScale, this._cachedVScale, 0, TmpVectors.Matrix[2]);
Matrix.TranslationToRef(this._cachedUOffset, this._cachedVOffset, 0, TmpVectors.Matrix[3]);
TmpVectors.Matrix[0].multiplyToRef(this._rowGenerationMatrix!, this._cachedTextureMatrix);
this._cachedTextureMatrix.multiplyToRef(TmpVectors.Matrix[1], this._cachedTextureMatrix);
this._cachedTextureMatrix.multiplyToRef(TmpVectors.Matrix[2], this._cachedTextureMatrix);
this._cachedTextureMatrix.multiplyToRef(TmpVectors.Matrix[3], this._cachedTextureMatrix);
// copy the translation row to the 3rd row of the matrix so that we don't need to update the shaders (which expects the translation to be on the 3rd row)
this._cachedTextureMatrix.setRowFromFloats(2, this._cachedTextureMatrix.m[12], this._cachedTextureMatrix.m[13], this._cachedTextureMatrix.m[14], 1);
} else {
this._prepareRowForTextureGeneration(0, 0, 0, this._t0!);
this._prepareRowForTextureGeneration(1.0, 0, 0, this._t1!);
this._prepareRowForTextureGeneration(0, 1.0, 0, this._t2!);
this._t1!.subtractInPlace(this._t0!);
this._t2!.subtractInPlace(this._t0!);
Matrix.FromValuesToRef(
this._t1!.x, this._t1!.y, this._t1!.z, 0.0,
this._t2!.x, this._t2!.y, this._t2!.z, 0.0,
this._t0!.x, this._t0!.y, this._t0!.z, 0.0,
0.0, 0.0, 0.0, 1.0,
this._cachedTextureMatrix
);
}
let scene = this.getScene();
if (!scene) {
return this._cachedTextureMatrix;
}
// We flag the materials that are using this texture as "texture dirty" because depending on the fact that the matrix is the identity or not, some defines
// will get different values (see MaterialHelper.PrepareDefinesForMergedUV), meaning we should regenerate the effect accordingly
scene.markAllMaterialsAsDirty(Constants.MATERIAL_TextureDirtyFlag, (mat) => {
return mat.hasTexture(this);
});
return this._cachedTextureMatrix;
}
```