]> git.kianting.info Git - clo/blob - src/libclo/index.ts
bb884fae1260a5fed6209a6895b835091f3a4607
[clo] / src / libclo / index.ts
1 import { isBoxedPrimitive, isKeyObject, isStringObject } from "util/types";
2 import {tkTree} from "../parser";
3 import {FontStyle, TextStyle, TextWeight, fontStyleTofont} from "../canva";
4 import { JSDOM } from "jsdom";
5 import * as fontkit from "fontkit";
6
7 /**
8 * TYPES
9 */
10
11 /**
12 * text direction
13 * LTR - left to right
14 * TTB - top to bottom
15 * etc.
16 */
17 export enum Direction{
18 LTR,
19 RTL,
20 TTB,
21 BTT,
22 }
23
24 /**
25 * frame box is a subclass of box
26 * - directionInsideLine : text direction inside a line
27 * - baselineskip : the distance between baselines in px
28 */
29 export interface FrameBox extends Box{
30 directionInsideLine : Direction,
31 baseLineskip : number | null,
32 }
33
34 export interface CharBox extends Box{
35 minX: number,
36 maxX: number,
37 minY: number,
38 maxY: number,
39
40 }
41
42 /**
43 * a basic Box
44 * - x :
45 * - y :
46 * - textStyle :
47 * - direction :
48 * - width : x_advance
49 * - content :
50 */
51 export interface Box{
52 x : number | null,
53 y : number | null,
54 textStyle : TextStyle | null,
55 direction : Direction,
56 width : number,
57 height : number,
58 content : string | Box[] | null,
59 }
60
61
62 /**
63 * DEFAULT CONST PART
64 */
65 export const A4_IN_PX = {"width" : 793.7,
66 "height" : 1122.5};
67
68 export const defaultTextStyle : TextStyle = {
69 family : "FreeSerif",
70 size : ptToPx(12),
71 textWeight : TextWeight.REGULAR,
72 fontStyle : FontStyle.ITALIC,
73 }
74
75 export const defaultFrameStyle : FrameBox = {
76 directionInsideLine : Direction.LTR,
77 direction : Direction.TTB,
78 baseLineskip : ptToPx(15),
79 textStyle : defaultTextStyle,
80 x : A4_IN_PX.width * 0.10,
81 y : A4_IN_PX.height * 0.10,
82 width : A4_IN_PX.width * 0.80,
83 height : A4_IN_PX.height * 0.80,
84 content : null,
85 };
86
87 /**
88 * definition for cjk scripts
89 * - Hani : Han Character
90 * - Hang : Hangul
91 * - Bopo : Bopomofo
92 * - Kana : Katakana
93 * - Hira : Hiragana
94 */
95 export const cjkvBlocksInRegex = ["Hani", "Hang", "Bopo", "Kana", "Hira"];
96
97 export const cjkvRegexPattern = new RegExp("((?:" +
98 cjkvBlocksInRegex.map((x)=>"\\p{Script_Extensions="+x+"}").join("|") + ")+)", "gu");
99 /**
100 * FUNCTION PART
101 */
102 /**
103 * convert from ptToPx
104 * @param pt pt size value
105 * @returns the corresponding px value
106 */
107 export function ptToPx(pt : number) : number{
108 return pt * 4.0 / 3.0;
109 }
110
111
112
113 /**
114 * REGISTER PART
115 */
116
117 /**
118 * convert '\n\n' to newline command ["nl"]
119 * @param arr the input `tkTree`
120 * @param clo the `Clo` object
121 * @returns the input tktree
122 */
123 export function twoReturnsToNewline(arr : tkTree, clo : Clo): tkTree{
124 var middle : tkTree = [];
125
126 for (let i = 0; i < arr.length; i++) {
127 var item = arr[i];
128 if (!Array.isArray(item)){
129 middle = middle.concat(item.split(/(\n\n)/g));
130 }
131 else{
132 middle.push(item);
133 }
134 }
135
136 var result : tkTree = [];
137 for (let j = 0; j < middle.length; j++){
138 var item = middle[j];
139 if (!Array.isArray(item) && item == "\n\n"){
140 result.push(["nl"]); // push a newline command to the result `tkTree`
141 }
142 else{
143 result.push(middle[j]);
144 }
145 }
146
147 return result;
148 }
149
150 /**
151 * split CJKV and non-CJKV
152 *
153 * @param arr : input tkTree
154 * @returns a splitted tkTree (by CJK and NonCJK)
155 * - Examples:
156 * ```
157 * [`many臺中daylight`] => [`many`, `臺中`, `dahylight`]
158 * ```
159 */
160 export function splitCJKV(arr : tkTree, clo : Clo): tkTree{
161 var result : tkTree = [];
162 for (let i = 0; i < arr.length; i++) {
163 var item = arr[i];
164 if (!Array.isArray(item)){
165 result = result.concat(item.split(cjkvRegexPattern));
166 }
167 else{
168 result.push(item);
169 }
170 }
171
172 return result;
173 }
174
175 /**
176 * hyphenation for a clo document
177 * @param arr the array for a `tkTree`
178 * @param clo the Clo object
179 */
180 export function hyphenForClo(arr : tkTree, clo : Clo): tkTree{
181 let hyphenLanguage : string = clo.attrs["hyphenLanguage"];
182 let res = hyphenTkTree(arr, hyphenLanguage);
183 return res;
184
185 }
186
187 /**
188 * convert spaces to Breakpoint
189 * \s+ => ["bp" [\s+] ""]
190 * @param arr the tkTree input text stream
191 * @param clo the Clo object
192 * @returns the converted object
193 */
194 export function spacesToBreakpoint(arr : tkTree, clo : Clo) : tkTree{
195 let spacePattern = /^([ \t]+)$/g;
196 var result : tkTree = [];
197 for (let i = 0; i < arr.length; i++){
198 var item = arr[i];
199 if (!Array.isArray(item) && item.match(spacePattern)){
200 result.push([ 'bp', item, "" ]); // push a newline command to the result `tkTree`
201 }
202 else{
203 result.push(item);
204 }
205 }
206
207 return result;
208 }
209
210 /**
211 * remove all the `` (empty string) in the arr
212 * @param arr the tkTree to be filtered
213 * @param clo the Clo file
214 */
215 export function filterEmptyString(arr : tkTree, clo : Clo) : tkTree{
216 if (Array.isArray(arr)){
217 arr.filter((x)=>{return x != ``;});
218 }
219
220 return arr;
221 }
222
223
224 /**
225 * OTHER FUNCTIONS
226 */
227
228 /**
229 * hyphenate for a tkTree
230 * - hyphenation => ["bp", "", "-"]
231 * @param arr the tkTree array
232 * @param lang ISO 639 code for the language
233 */
234 export function hyphenTkTree(arr : tkTree, lang: string) : tkTree{
235 // import corresponding hyphen language data and function
236 let hyphen = require("hyphen/"+lang);
237
238 let result :tkTree[] = [];
239 for (let i = 0; i < arr.length; i++) {
240 let element = arr[i];
241 let splitter = "分"; // a CJKV
242 if (!Array.isArray(element)){
243 let hyphenatedElement : string = hyphen.hyphenateSync(element, {hyphenChar :splitter});
244 let hyphenatedSplitted : tkTree = hyphenatedElement.split(splitter);
245 var newSplitted : tkTree = [];
246 for (var j=0; j<hyphenatedSplitted.length-1;j++){
247 newSplitted.push(hyphenatedSplitted[j]);
248 // "bp" for breakpoint
249 newSplitted.push(["bp", "", "-"]); //insert a breakable point (bp) mark
250 }
251 newSplitted.push(hyphenatedSplitted[hyphenatedSplitted.length-1]);
252
253 result = result.concat(newSplitted);
254
255 }else{
256 result.push(element);
257 }
258
259 }
260
261 return result;
262 }
263
264 /**
265 * calculate the text width and Height with a given `TextStyle`
266 * @param preprocessed
267 * @param defaultFontStyle
268 */
269 export async function calculateTextWidthHeight(element : tkTree, style : TextStyle): Promise<any> {
270 var res = [];
271
272 for (var i=0; i<element.length; i++){
273 res.push(await calculateTextWidthHeightAux(element[i], style));
274 }
275
276 console.log(res);
277
278 return res;
279 }
280
281
282 /**
283 * calculate the text width and Height with a given `TextStyle`
284 * @param preprocessed
285 * @param defaultFontStyle
286 */
287 export async function calculateTextWidthHeightAux(element : tkTree, style : TextStyle): Promise<any> {
288 var result : any = [];
289
290
291
292 let fontPair = fontStyleTofont(style);
293 if (fontPair.path.match(/\.ttc$/)){
294 var font = await fontkit.openSync(fontPair.path, fontPair.psName);
295 }
296 else{
297 var font = await fontkit.openSync(fontPair.path);
298 }
299 if (!Array.isArray(element)){
300 var run = font.layout(element, undefined, undefined, undefined, "ltr");
301
302
303
304 for (var j=0;j<run.glyphs.length;j++){
305 let runGlyphsItem = run.glyphs[j];
306
307
308 let item : CharBox = {
309 x : null,
310 y : null,
311 textStyle : style,
312 direction : Direction.LTR,
313 width : (runGlyphsItem.advanceWidth)*(style.size)/1000,
314 height : (runGlyphsItem.bbox.maxY - runGlyphsItem.bbox.minY)*(style.size)/1000,
315 content : element[j],
316 minX : runGlyphsItem.bbox.minX,
317 maxX : runGlyphsItem.bbox.maxX,
318 minY : runGlyphsItem.bbox.minY,
319 maxY : runGlyphsItem.bbox.maxY
320 }
321
322 result.push(item);
323
324 }
325 return result;
326
327
328
329
330 }else if(element[0] == "bp"){
331 let beforeNewLine = await calculateTextWidthHeightAux(element[1], style);
332 let afterNewLine = await calculateTextWidthHeightAux(element[2], style);
333
334 return ["bp", beforeNewLine, afterNewLine];
335 }else{
336 return calculateTextWidthHeight(element[1], style);
337 }
338 }
339
340
341
342
343 /**
344 * whole document-representing class
345 */
346 export class Clo{
347 /** storing the text string into the main frame */
348 mainStream : Array<string>;
349 /** array of preprocessor functions to preprocess the `mainStream` */
350 preprocessors : Array<Function>;
351 /** the attributes for the Clo */
352 attrs: {[index: string]:any} ; // a4 size(x,y)
353
354
355 constructor(){
356 this.preprocessors = [];
357 this.mainStream = [];
358 this.attrs = {
359 "page" : A4_IN_PX, // default for a4. in px of [x, y]
360 "defaultFrameStyle" : defaultFrameStyle, // defaultFrameStyle
361 "hyphenLanguage" : 'en' // hyphenated in the language (in ISO 639)
362 };
363
364
365
366 // register the precessor functions
367 this.preprocessorRegister(splitCJKV);
368 this.preprocessorRegister(hyphenForClo);
369 this.preprocessorRegister(twoReturnsToNewline);
370 this.preprocessorRegister(spacesToBreakpoint);
371 this.preprocessorRegister(filterEmptyString);
372 }
373
374 public setAttr(attr : string, val : any):void{
375 Object.assign(this.attrs, attr, val);
376 }
377
378 public getAttr(attr:string) : any{
379 if (Object.keys(this.attrs).length === 0){
380 return this.attrs[attr];
381 }else{
382 return undefined;
383 }
384
385 }
386
387 /**
388 * register a function of preprocessor
389 * @param f a function
390 */
391 public preprocessorRegister(f : Function){
392 this.preprocessors.push(f);
393 }
394
395 public generatePdf(){
396 // preprocessed
397 var preprocessed = this.mainStream;
398 for (var i = 0; i<this.preprocessors.length; i++){
399 preprocessed = this.preprocessors[i](preprocessed, this);
400 }
401 // generate the width and height of the stream
402
403 let defaultFontStyle : TextStyle = this.attrs["defaultFrameStyle"].textStyle;
404 calculateTextWidthHeight(preprocessed, defaultFontStyle);
405
406 // TODO
407 console.log(preprocessed);
408 }
409
410
411 }
412
413 /*
414 export let a = new Clo();
415 export default a; */