海屿文章列表

获取系统字体列表两种思路

浏览器没有获取本地字体列表的API,同时FLash也被浏览抛弃。需要一种新的方式判断字体是否支持。
下图列出了一些字体,特征各不一样。最直观的是字形,其次不同字体下的相同文字的宽度也是不一样的,由此我们能通过这两点差异对字体进行判断。
ziti.jpg

字体宽度

每种字体的宽度都不尽相同。
我们可以通过分别测量默认字体和目标字体的宽度,查看二者的差异。若果不一致则说明目标字体可用,否则说明不支持该字体,浏览器会退到了默认字体。

font-load-detection-three.c10e6f652d.png

在canvas中可以使用measureText测量文字渲染的宽度,这个宽度非常精确。

var canvas = document.createElement('canvas')
var ctx = canvas.getContext("2d");
var width = ctx.measureText('123').width; // 21.111328125

使用ctx.font可以设置字号和字体,例如ctx.font="30px Arial",默认为10px sans-serif

我们可以提前计算好各种字体的宽度表,就可以快速地识别字体样式。

像素比对

根据用户设置的字体将某一个字符绘制在canvas上,并提取像素信息与默认字体进行比对,看是否一致。
这种方法最为准确,当然缺点也很明显就是计算量较大。

let isSupportFontFamily = function (f) {
  let base = "Arial";
  if (f.toLowerCase() == base.toLowerCase()) return true
  let e = "a";
  let d = 100;
  let a = 100, i = 100;
  let c = document.createElement("canvas");
  let b = c.getContext("2d");
  c.width = a;
  c.height = i;
  b.textAlign = "center";
  b.fillStyle = "black";
  b.textBaseline = "middle";
  let g = function (j) {
    b.clearRect(0, 0, a, i);
    b.font = d + "px " + j + ", " + base;
    b.fillText(e, a / 2, i / 2);
    let k = b.getImageData(0, 0, a, i).data;
    return [].slice.call(k).filter(l => l != 0);
  };

  return g(base).join("") !== g(f).join("");
};

nest.js 使用 node-http-proxy 导致请求阻塞

问题的原因是代理中间件与body-parser中间件发生了冲突。请求体的解析如果在转发之前,那么后端服务将会收不到请求结束的消息,导致请求一直 pending。

nest默认是启用body-parser的,需要在初始化时将其关闭。
const app = await NestFactory.create(AppModule, { bodyParser: false });

然后需要为不需要代理的请求添加body-parser中间件。body-parser包括了json,urlencoded和text的解析,根据情况按需引用。

const unless = (path, middleware) => (req, res, next) => {
  if (req.path.indexOf(path) !== -1) {
    return next();
  } else {
    return middleware(req, res, next);
  }
};


app.use(unless('/db', json()));
app.use(unless('/db', urlencoded({ extended: false })));

将DOM节点转换为图片

如何将DOM转换为图片,这个问题可能有点大,一时想不到答案,但我们把问题缩小一下,转换为SVG你可能就想起来了。
SVG能直接嵌入到HTML中,那反过来应该也行。回忆一下,SVG和DOM是不是长得很像?因为他们都是XML的一种方言,所以我们把DOM塞到SVG里面就行了。但是,问题来了,XHTML和SVG都有一个<title>标签,SVG怎么区分他们?其实这就好比变量之于命名空间,XML有各种方言,所以也有命名空间开区分它们。

<svg xmlns="http://www.w3.org/2000/svg">

SVG的开头一般长这个样子,这个xmlns就是xml name space的缩写。http://www.w3.org/2000/svg就是SVG的命名空间。
HTML的命名空间是<html xmlns="http://www.w3.org/1999/xhtml">

在SVG中引入其他命名空间要放在foreignObject标签内。

最后我们可以先将SVG转换为canvas,再通过canvastoDataURL方法来将其转换为对应格式的bash64编码。

如果你要转换DOM节点的比较复杂,推荐使用dom-to-image库来实现。

Object.create() 与 new 的区别

我们先看一下二者具体做了些什么:

new Test():

  • create new Object() obj
  • set obj.__proto__ to Test.prototype
  • return Test.call(obj) || obj;

最后一步如果构造函数未返回对象,则将该新对象返回,即this,如返回了对象,则替换this。

Object.create( Test.prototype )

  • create new Object() obj
  • set obj.__proto__ to Test.prototype
  • return obj;

所以说二者区别:
new X= Object.create( X.prototype )+ X.constructor()

看下面一个例子:

function Dog1(name){
   return {dogName: name};
}

function Dog2(name){}

Dog1.prototype.setName = Dog2.prototype.setName = function(name){
    this.name = name;
};

let pet1 = new Dog1('rocky'); 
console.log(pet1.dogName);           //rocky  
pet1.setName('skye');                //error

pet1 = new Dog2();
pet1.setName('skye');
console.log(pet1.name);              //skye            

let pet2 = Object.create(Dog1.prototype);
pet2.setName('skye');                //ok
console.log(pet2.name);              //skye
console.log(pet2.dogName);           //undefined

Dog1方法返回一个对象,所以替换了this,而我们使用Dog2就能访问原型上的方法了。

参考文章:
Understanding the difference between Object.create() and new SomeFunction()

JS 实现位图(Bitset)数据结构

位图数组中每个元素在内存中占用1位,所以可以节省存储空间,在索引,数据压缩等方面有广泛应用。
我们使用类型化数组Uint8Array储存位图,数组中每一个元素占8个字节所以最大值是255。关键是寻找偏移量,index>>3 右移三位相当于除8。可以找到对应的数组元素,1 << (index % 8) 找到位对应数组元素的哪一个字节。最后通过按位判断该为1或0。

判断存在时:使用按位与&(只有两个操作数相应的比特位都是1时,结果才为1)
修改某项:使用按位或|

Typescript代码如下:

export class Bitmap {
    private bin: Uint8Array;

    constructor(size: number) {
        this.bin = new Uint8Array(new ArrayBuffer((size >> 3) + 1));
    }

    public get(index: number): boolean {
        const row = index >> 3;
        const bit = 1 << (index % 8);
        return (this.bin[row] & bit) > 0;
    }

    public set(index: number, bool: boolean = true) {
        const row = index >> 3;
        const bit = 1 << (index % 8);
        if (bool) {
            this.bin[row] |= bit;
        } else {
            this.bin[row] &= (255 ^ bit);
        }
    };

    public fill(num: number = 0) {
        this.bin.fill(num);
    }

    public flip(index: number) {
        const row = Math.floor(index / 8);
        const bit = 1 << (index % 8);
        this.bin[row] ^= bit;
    }
}