]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - bin/bmpf.js
import terminus-font-4.48
[FreeBSD/FreeBSD.git] / bin / bmpf.js
1 //
2 // Copyright (c) 2018 Dimitar Toshkov Zhekov <dimitar.zhekov@gmail.com>
3 //
4 // This program is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU General Public License as
6 // published by the Free Software Foundation; either version 2 of
7 // the License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14
15 'use strict';
16
17 const fnutil = require('./fnutil.js');
18 const bdf = require('./bdf.js');
19
20
21 class Char {
22         constructor(code, name, width, data) {
23                 this.code = code;
24                 this.name = name;
25                 this.width = width;
26                 this.data = data;
27         }
28
29         static from(char, fbbox) {
30                 const deltaYOff = char.bbx.yoff - fbbox.yoff;  // ~DSB
31                 let width;
32                 let dstXOff;
33
34                 if (char.dwidth.x >= 0) {
35                         if (char.bbx.xoff >= 0) {
36                                 width = Math.max(char.bbx.width + char.bbx.xoff, char.dwidth.x);
37                                 dstXOff = char.bbx.xoff;
38                         } else {
39                                 width = Math.max(char.bbx.width, char.dwidth.x - char.bbx.xoff);
40                                 dstXOff = 0;
41                         }
42                 } else {
43                         dstXOff = Math.max(char.bbx.xoff - char.dwidth.x, 0);
44                         width = char.bbx.width + dstXOff;
45                 }
46
47                 if (width > bdf.WIDTH_MAX) {
48                         throw new Error(`char ${char.code}: output width > ${bdf.WIDTH_MAX}`);
49                 }
50                 if (char.bbx.yoff < fbbox.yoff) {
51                         throw new Error(`char ${char.code}: BBX yoff < FONTBOUNDINGBOX yoff`);
52                 }
53
54                 const height = fbbox.height;
55                 const srcRowSize = char.bbx.rowSize();
56                 const dstRowSize = (width + 7) >> 3;
57                 const dstYMax = height - deltaYOff;
58                 const dstYMin = dstYMax - char.bbx.height;
59                 const compatRow = dstXOff === 0 && width >= char.bbx.width;
60                 let data;
61
62                 if (compatRow && srcRowSize === dstRowSize && dstYMin === 0 && dstYMax === height) {
63                         data = char.data;
64                 } else if (dstYMin < 0) {
65                         throw new Error(`char ${char.code}: start row ${dstYMin}`);
66                 } else {
67                         data = Buffer.alloc(dstRowSize * height);
68
69                         for (let dstY = dstYMin; dstY < dstYMax; dstY++) {
70                                 let srcByteNo = (dstY - dstYMin) * srcRowSize;
71                                 let dstByteNo = dstY * dstRowSize + (dstXOff >> 3);
72
73                                 if (compatRow) {
74                                         char.data.copy(data, dstByteNo, srcByteNo, srcByteNo + srcRowSize);
75                                 } else {
76                                         let srcBitNo = 7;
77                                         let dstBitNo = 7 - (dstXOff & 7);
78
79                                         for (let x = 0; x < char.bbx.width; x++) {
80                                                 if (char.data[srcByteNo] & (1 << srcBitNo)) {
81                                                         data[dstByteNo] |= (1 << dstBitNo);
82                                                 }
83                                                 if (--srcBitNo < 0) {
84                                                         srcBitNo = 7;
85                                                         srcByteNo++;
86                                                 }
87                                                 if (--dstBitNo < 0) {
88                                                         dstBitNo = 7;
89                                                         dstByteNo++;
90                                                 }
91                                         }
92                                 }
93                         }
94                 }
95
96                 return new Char(char.code, char.props.get('STARTCHAR'), width, data);
97         }
98
99         packedSize() {
100                 return (this.width * (this.data.length / this.rowSize()) + 7) >> 3;
101         }
102
103         rowSize() {
104                 return (this.width + 7) >> 3;
105         }
106
107         write(output, height, yoffset) {
108                 let header = `STARTCHAR ${this.name}\nENCODING ${this.code}\n`;
109                 const swidth = fnutil.round(this.width * 1000 / height);
110
111                 header += `SWIDTH ${swidth} 0\nDWIDTH ${this.width} 0\nBBX ${this.width} ${height} 0 ${yoffset}\n`;
112                 output.writeLine(header + 'BITMAP\n' + bdf.Char.bitmap(this.data, this.rowSize()) + 'ENDCHAR');
113         }
114 }
115
116
117 class Font extends bdf.Font {
118         constructor() {
119                 super();
120                 this.minWidth = bdf.WIDTH_MAX;
121                 this.avgWidth = 0;
122         }
123
124         _read(input) {
125                 let totalWidth = 0;
126
127                 super._read(input);
128                 this.chars = this.chars.map(char => Char.from(char, this.bbx));
129                 this.bbx.xoff = 0;
130                 this.chars.forEach(char => {
131                         this.minWidth = Math.min(this.minWidth, char.width);
132                         this.bbx.width = Math.max(this.bbx.width, char.width);
133                         totalWidth += char.width;
134                 });
135                 this.avgWidth = fnutil.round(totalWidth / this.chars.length);
136                 this.props.set('FONTBOUNDINGBOX', this.bbx.toString());
137                 return this;
138         }
139
140         static read(input) {
141                 return (new Font())._read(input);
142         }
143
144         getProportional() {
145                 return Number(this.bbx.width > this.minWidth);
146         }
147
148         write(output) {
149                 this.props.forEach((name, value) => output.writeProp(name, value));
150                 this.chars.forEach(char => char.write(output, this.bbx.height, this.bbx.yoff));
151                 output.writeLine('ENDFONT');
152         }
153 }
154
155
156 module.exports = Object.freeze({
157         Char,
158         Font
159 });