]>
git.kianting.info Git - clo/blob - 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 import * as util from
"node:util";
7 import * as breakLines from
"./breakLines";
19 export enum Direction
{
28 * - stretchFactor : the stretch factor in float
30 export interface HGlue
{
34 export interface BreakPoint
{
39 export type BoxesItem
= HGlue
| Box
| BreakPoint
| BoxesItem
[] ;
42 * frame box is a subclass of box
43 * - directionInsideLine : text direction inside a line
44 * - baselineskip : the distance between baselines in px
46 export interface FrameBox
extends Box
{
47 directionInsideLine
: Direction
,
48 baseLineskip
: number | null,
51 export interface CharBox
extends Box
{
71 textStyle
: TextStyle
| null,
72 direction
: Direction
,
75 content
: string | Box
[] | null,
82 export const A4_IN_PX
= {"width" : 793.7,
85 export const defaultTextStyle
: TextStyle
= {
88 textWeight
: TextWeight
.REGULAR
,
89 fontStyle
: FontStyle
.ITALIC
,
92 export const defaultFrameStyle
: FrameBox
= {
93 directionInsideLine
: Direction
.LTR
,
94 direction
: Direction
.TTB
,
95 baseLineskip
: ptToPx(15),
96 textStyle
: defaultTextStyle
,
97 x
: A4_IN_PX
.width
* 0.10,
98 y
: A4_IN_PX
.height
* 0.10,
99 width
: A4_IN_PX
.width
* 0.80,
100 height
: A4_IN_PX
.height
* 0.80,
105 * definition for cjk scripts
106 * - Hani : Han Character
112 export const cjkvBlocksInRegex
= ["Hani", "Hang", "Bopo", "Kana", "Hira"];
114 export const cjkvRegexPattern
= new RegExp("((?:" +
115 cjkvBlocksInRegex
.map((x
)=>"\\p{Script_Extensions="+x
+"}").join("|") + ")+)", "gu");
120 * convert from ptToPx
121 * @param pt pt size value
122 * @returns the corresponding px value
124 export function ptToPx(pt
: number) : number{
125 return pt
* 4.0 / 3.0;
135 * convert '\n\n' to newline command ["nl"]
136 * @param arr the input `tkTree`
137 * @param clo the `Clo` object
138 * @returns the input tktree
140 export function twoReturnsToNewline(arr
: tkTree
, clo
: Clo
): tkTree
{
141 var middle
: tkTree
= [];
143 for (let i
= 0; i
< arr
.length
; i
++) {
145 if (!Array.isArray(item
)){
146 middle
= middle
.concat(item
.split(/(\n\n)/g
));
153 var result
: tkTree
= [];
154 for (let j
= 0; j
< middle
.length
; j
++){
155 var item
= middle
[j
];
156 if (!Array.isArray(item
) && item
== "\n\n"){
157 result
.push(["nl"]); // push a newline command to the result `tkTree`
160 result
.push(middle
[j
]);
168 * split CJKV and non-CJKV
170 * @param arr : input tkTree
171 * @returns a splitted tkTree (by CJK and NonCJK)
174 * [`many臺中daylight`] => [`many`, `臺中`, `dahylight`]
177 export function splitCJKV(arr
: tkTree
, clo
: Clo
): tkTree
{
178 var result
: tkTree
= [];
179 for (let i
= 0; i
< arr
.length
; i
++) {
181 if (!Array.isArray(item
)){
182 result
= result
.concat(item
.split(cjkvRegexPattern
));
193 * hyphenation for a clo document
194 * @param arr the array for a `tkTree`
195 * @param clo the Clo object
197 export function hyphenForClo(arr
: tkTree
, clo
: Clo
): tkTree
{
198 let hyphenLanguage
: string = clo
.attrs
["hyphenLanguage"];
199 let res
= hyphenTkTree(arr
, hyphenLanguage
);
205 * convert spaces to Breakpoint
206 * \s+ => ["bp" [\s+] ""]
207 * @param arr the tkTree input text stream
208 * @param clo the Clo object
209 * @returns the converted object
211 export function spacesToBreakpoint(arr
: tkTree
, clo
: Clo
) : tkTree
{
212 let spacePattern
= /^([ \t]+)$
/g
;
213 var result
: tkTree
= [];
214 for (let i
= 0; i
< arr
.length
; i
++){
216 if (!Array.isArray(item
) && item
.match(spacePattern
)){
217 // push a breakpoint command to the result `tkTree`
218 result
.push([ 'bp', [["hglue", "0.1"], item
] , "" ]);
229 * remove all the `` (empty string) in the arr
230 * @param arr the tkTree to be filtered
231 * @param clo the Clo file
233 export function filterEmptyString(arr
: tkTree
, clo
: Clo
) : tkTree
{
234 if (Array.isArray(arr
)){
235 arr
.filter((x
)=>{return x
!= ``;});
247 * hyphenate for a tkTree
248 * - hyphenation => ["bp", "", "-"]
249 * @param arr the tkTree array
250 * @param lang ISO 639 code for the language
252 export function hyphenTkTree(arr
: tkTree
, lang
: string) : tkTree
{
253 // import corresponding hyphen language data and function
254 let hyphen
= require("hyphen/"+lang
);
256 let result
:tkTree
[] = [];
257 for (let i
= 0; i
< arr
.length
; i
++) {
258 let element
= arr
[i
];
259 let splitter
= "分"; // a CJKV
260 if (!Array.isArray(element
)){
261 let hyphenatedElement
: string = hyphen
.hyphenateSync(element
, {hyphenChar
:splitter
});
262 let hyphenatedSplitted
: tkTree
= hyphenatedElement
.split(splitter
);
263 var newSplitted
: tkTree
= [];
264 for (var j
=0; j
<hyphenatedSplitted
.length
-1;j
++){
265 newSplitted
.push(hyphenatedSplitted
[j
]);
266 // "bp" for breakpoint
267 newSplitted
.push(["bp", "", "-"]); //insert a breakable point (bp) mark
269 newSplitted
.push(hyphenatedSplitted
[hyphenatedSplitted
.length
-1]);
271 result
= result
.concat(newSplitted
);
274 result
.push(element
);
283 * calculate the text width and Height with a given `TextStyle`
284 * @param preprocessed
285 * @param defaultFontStyle
287 export async function calculateTextWidthHeight(element
: tkTree
, style
: TextStyle
): Promise
<BoxesItem
[]> {
290 for (var i
=0; i
<element
.length
; i
++){
291 res
.push(await calculateTextWidthHeightAux(element
[i
], style
));
301 * calculate the text width and Height with a given `TextStyle`
302 * @param preprocessed
303 * @param defaultFontStyle
305 export async function calculateTextWidthHeightAux(element
: tkTree
, style
: TextStyle
): Promise
<BoxesItem
> {
306 var result
: BoxesItem
= [];
310 let fontPair
= fontStyleTofont(style
);
311 if (fontPair
.path
.match(/\
.ttc$
/)){
312 var font
= await fontkit
.openSync(fontPair
.path
, fontPair
.psName
);
315 var font
= await fontkit
.openSync(fontPair
.path
);
317 if (!Array.isArray(element
)){
318 var run
= font
.layout(element
, undefined, undefined, undefined, "ltr");
322 for (var j
=0;j
<run
.glyphs
.length
;j
++){
323 let runGlyphsItem
= run
.glyphs
[j
];
326 let item
: CharBox
= {
330 direction
: Direction
.LTR
,
331 width
: (runGlyphsItem
.advanceWidth
)*(style
.size
)/1000,
332 height
: (runGlyphsItem
.bbox
.maxY
- runGlyphsItem
.bbox
.minY
)*(style
.size
)/1000,
333 content
: element
[j
],
334 minX
: runGlyphsItem
.bbox
.minX
,
335 maxX
: runGlyphsItem
.bbox
.maxX
,
336 minY
: runGlyphsItem
.bbox
.minY
,
337 maxY
: runGlyphsItem
.bbox
.maxY
348 }else if(element
[0] == "bp"){
350 var beforeNewLine
= await calculateTextWidthHeightAux(element
[1], style
);
351 if (Array.isArray(beforeNewLine
)){
352 beforeNewLine
= beforeNewLine
.flat();
355 let afterNewLine
= await calculateTextWidthHeightAux(element
[2], style
);
356 if (Array.isArray(afterNewLine
)){
357 afterNewLine
= afterNewLine
.flat();
360 let breakPointNode
: BreakPoint
= {
361 original
: beforeNewLine
,
362 newLined
: afterNewLine
,
365 return breakPointNode
;
366 }else if(element
[0] == "hglue" && !Array.isArray(element
[1])){
367 let hGlue
: HGlue
= {stretchFactor
: parseFloat(element
[1])}
371 return calculateTextWidthHeight(element
, style
);
379 * whole document-representing class
382 /** storing the text string into the main frame */
383 mainStream
: Array<string>;
384 /** array of preprocessor functions to preprocess the `mainStream` */
385 preprocessors
: Array<Function>;
386 /** the attributes for the Clo */
387 attrs
: {[index
: string]:any} ; // a4 size(x,y)
391 this.preprocessors
= [];
392 this.mainStream
= [];
394 "page" : A4_IN_PX
, // default for a4. in px of [x, y]
395 "defaultFrameStyle" : defaultFrameStyle
, // defaultFrameStyle
396 "hyphenLanguage" : 'en' // hyphenated in the language (in ISO 639)
401 // register the precessor functions
402 this.preprocessorRegister(splitCJKV
);
403 this.preprocessorRegister(hyphenForClo
);
404 this.preprocessorRegister(twoReturnsToNewline
);
405 this.preprocessorRegister(spacesToBreakpoint
);
406 this.preprocessorRegister(filterEmptyString
);
409 public setAttr(attr
: string, val
: any):void{
410 Object.assign(this.attrs
, attr
, val
);
413 public getAttr(attr
:string) : any{
414 if (Object.keys(this.attrs
).length
=== 0){
415 return this.attrs
[attr
];
423 * register a function of preprocessor
424 * @param f a function
426 public preprocessorRegister(f
: Function){
427 this.preprocessors
.push(f
);
430 public async generatePdf(){
432 var preprocessed
= this.mainStream
;
433 for (var i
= 0; i
<this.preprocessors
.length
; i
++){
434 preprocessed
= this.preprocessors
[i
](preprocessed
, this);
436 // generate the width and height of the stream
438 let defaultFontStyle
: TextStyle
= this.attrs
["defaultFrameStyle"].textStyle
;
439 let a
= await calculateTextWidthHeight(preprocessed
, defaultFontStyle
);
442 console
.log(util
.inspect(a
, true, 100));
443 console
.log(breakLines
.totalCost(a
,3,100));
450 export let a = new Clo();