]> git.kianting.info Git - clo/blob - src/libclo/index.ts
update documentation link
[clo] / src / libclo / index.ts
1 import {tkTree} from "../parser";
2 import {FontStyle, TextStyle, TextWeight, fontStyleTofont, fontPathPSNamePair} from "../canva";
3 import * as fontkit from "fontkit";
4 import * as breakLines from "./breakLines";
5 const PDFDocument = require('pdfkit');
6 import * as fs from "fs";
7 import { Style } from "util";
8 import { time } from "console";
9 import {memfs} from "memfs";
10
11
12 /**
13 * TYPES
14 */
15
16 /**
17 * text direction
18 * LTR - left to right
19 * TTB - top to bottom
20 * etc.
21 */
22 export enum Direction{
23 LTR,
24 RTL,
25 TTB,
26 BTT,
27 }
28 /**
29 * Horizonal glue.
30 * - stretchFactor : the stretch factor in float
31 */
32 export interface HGlue{
33 isHorizonalGlue : true,
34 stretchFactor: number
35 }
36
37 export interface VGlue{
38 isHorizonalGlue : false,
39 stretchFactor: number
40 }
41
42 export interface BreakPoint{
43 original : BoxesItem,
44 newLined : BoxesItem
45 }
46
47 /** BR is like html br */
48 export interface BR extends BreakPoint{
49 isBR : true;
50 }
51
52 export type BoxesItem = HGlue | Box | BreakPoint | BoxesItem[] ;
53
54 /**
55 * frame box is a subclass of box
56 * - directionInsideLine : text direction inside a line
57 * - baselineskip : the distance between baselines in px
58 */
59 export interface FrameBox extends Box{
60 directionInsideLine : Direction,
61 baseLineskip : number | null,
62 }
63
64 export interface CharBox extends Box{
65 minX: number,
66 maxX: number,
67 minY: number,
68 maxY: number,
69
70 }
71
72 /**
73 * a basic Box
74 * - x : pt
75 * - y : pt
76 * - textStyle :
77 * - direction :
78 * - width : x_advance pt
79 * - content :
80 */
81 export interface Box{
82 x : number | null,
83 y : number | null,
84 textStyle : TextStyle | null,
85 direction : Direction,
86 width : number,
87 height : number,
88 content : string | Box[] | null,
89 }
90
91
92 /**
93 * DEFAULT CONST PART
94 */
95 export const A4_IN_PX = {"width" : 793.7,
96 "height" : 1122.5};
97
98 export const defaultTextStyle : TextStyle = {
99 family : "Noto Sans CJK TC",
100 size : ptToPx(12),
101 textWeight : TextWeight.REGULAR,
102 fontStyle : FontStyle.ITALIC,
103 }
104
105 export const defaultFrameStyle : FrameBox = {
106 directionInsideLine : Direction.LTR,
107 direction : Direction.TTB,
108 baseLineskip : ptToPx(15),
109 textStyle : defaultTextStyle,
110 x : A4_IN_PX.width * 0.10 ,
111 y : A4_IN_PX.height * 0.10 ,
112 width : A4_IN_PX.width * 0.80 ,
113 height : A4_IN_PX.height * 0.80 ,
114 content : null,
115 };
116
117 /**
118 * definition for cjk scripts
119 * - Hani : Han Character
120 * - Hang : Hangul
121 * - Bopo : Bopomofo
122 * - Kana : Katakana
123 * - Hira : Hiragana
124 */
125 export const cjkvBlocksInRegex = ["Hani", "Hang", "Bopo", "Kana", "Hira"];
126
127 export const cjkvRegexPattern = new RegExp("((?:" +
128 cjkvBlocksInRegex.map((x)=>"\\p{Script_Extensions="+x+"}").join("|") + ")+)", "gu");
129 /**
130 * FUNCTION PART
131 */
132 /**
133 * convert from ptToPx
134 * @param pt pt size value
135 * @returns the corresponding px value
136 */
137 export function ptToPx(pt : number) : number{
138 return pt * 4.0 / 3.0;
139 }
140
141
142
143 /**
144 * REGISTER PART
145 */
146
147 /**
148 * convert '\n\n' to new paragraph command ["br"]
149 * @param arr the input `tkTree`
150 * @param clo the `Clo` object
151 * @returns the input tktree
152 */
153 export function twoReturnsToNewline(arr : tkTree, clo : Clo): tkTree{
154 var middle : tkTree = [];
155
156 for (let i = 0; i < arr.length; i++) {
157 var item = arr[i];
158 if (!Array.isArray(item)){
159 middle = middle.concat(item.split(/(\n\n)/g));
160 }
161 else{
162 middle.push(item);
163 }
164 }
165
166 var result : tkTree = [];
167 for (let j = 0; j < middle.length; j++){
168 var item = middle[j];
169 if (!Array.isArray(item) && item == "\n\n"){
170 result.push(["br"]); // push a newline command to the result `tkTree`
171 }
172 else{
173 result.push(middle[j]);
174 }
175 }
176
177 return result;
178 }
179
180 /**
181 * split CJKV and non-CJKV
182 *
183 * @param arr : input tkTree
184 * @returns a splitted tkTree (by CJK and NonCJK)
185 * - Examples:
186 * ```
187 * [`many臺中daylight`] => [`many`, `臺中`, `dahylight`]
188 * ```
189 */
190 export function splitCJKV(arr : tkTree, clo : Clo): tkTree{
191 var result : tkTree = [];
192 for (let i = 0; i < arr.length; i++) {
193 var item = arr[i];
194 if (!Array.isArray(item)){
195 result = result.concat(item.split(cjkvRegexPattern));
196 }
197 else{
198 result.push(item);
199 }
200 }
201
202 return result;
203 }
204
205 /**
206 * hyphenation for a clo document
207 * @param arr the array for a `tkTree`
208 * @param clo the Clo object
209 */
210 export function hyphenForClo(arr : tkTree, clo : Clo): tkTree{
211 let hyphenLanguage : string = clo.attrs["hyphenLanguage"];
212 let res = hyphenTkTree(arr, hyphenLanguage);
213 return res;
214
215 }
216
217 /**
218 * convert spaces to Breakpoint
219 * \s+ => ["bp" [\s+] ""]
220 * @param arr the tkTree input text stream
221 * @param clo the Clo object
222 * @returns the converted object
223 */
224 export function spacesToBreakpoint(arr : tkTree, clo : Clo) : tkTree{
225 let spacePattern = /^([ \t]+)$/g;
226 var result : tkTree = [];
227 for (let i = 0; i < arr.length; i++){
228 var item = arr[i];
229 if (!Array.isArray(item) && item.match(spacePattern)){
230 // push a breakpoint command to the result `tkTree`
231 result.push([ 'bp', [["hglue", "0.1"], item] , "" ]);
232 }
233 else{
234 result.push(item);
235 }
236 }
237
238 return result;
239 }
240
241 /**
242 * remove all the `` (empty string) in the arr
243 * @param arr the tkTree to be filtered
244 * @param clo the Clo file
245 */
246 export function filterEmptyString(arr : tkTree, clo : Clo) : tkTree{
247 if (Array.isArray(arr)){
248 arr.filter((x)=>{return x != ``;});
249 }
250
251 return arr;
252 }
253
254
255 /**
256 * OTHER FUNCTIONS
257 */
258
259 /**
260 * hyphenate for a tkTree
261 * - hyphenation => ["bp", "", "-"]
262 * @param arr the tkTree array
263 * @param lang ISO 639 code for the language
264 */
265 export function hyphenTkTree(arr : tkTree, lang: string) : tkTree{
266 // import corresponding hyphen language data and function
267 let hyphen = require("hyphen/"+lang);
268
269 let result :tkTree[] = [];
270 for (let i = 0; i < arr.length; i++) {
271 let element = arr[i];
272 let splitter = "分"; // a CJKV
273 if (!Array.isArray(element)){
274 let hyphenatedElement : string = hyphen.hyphenateSync(element, {hyphenChar :splitter});
275 let hyphenatedSplitted : tkTree = hyphenatedElement.split(splitter);
276 var newSplitted : tkTree = [];
277 for (var j=0; j<hyphenatedSplitted.length-1;j++){
278 newSplitted.push(hyphenatedSplitted[j]);
279 // "bp" for breakpoint
280 newSplitted.push(["bp", "", "-"]); //insert a breakable point (bp) mark
281 }
282 newSplitted.push(hyphenatedSplitted[hyphenatedSplitted.length-1]);
283
284 result = result.concat(newSplitted);
285
286 }else{
287 result.push(element);
288 }
289
290 }
291
292 return result;
293 }
294
295
296
297 /**
298 * calculate the text width and Height with a given `TextStyle`
299 * @param preprocessed
300 * @param defaultFontStyle
301 */
302 export async function calculateTextWidthHeight(element : tkTree, style : TextStyle): Promise<BoxesItem[]> {
303 var res = [];
304 var styleCache = {};
305 var fontCache = {};
306
307 for (var i=0; i<element.length; i++){
308 let item = await calculateTextWidthHeightAux(element[i], style, <TextStyle>styleCache, <fontkit.Font>fontCache);
309 styleCache = item[1];
310 fontCache = item[2];
311 res.push(item[0]);
312 }
313
314 res = res.flat();
315
316 return res;
317 }
318
319
320 /**
321 * calculate the text width and Height with a given `TextStyle`
322 * @param preprocessed
323 * @param defaultFontStyle
324 */
325 export async function calculateTextWidthHeightAux(element : tkTree,
326 style : TextStyle,
327 styleCache : TextStyle,
328 fontCache : fontkit.Font): Promise<[BoxesItem, TextStyle, fontkit.Font] > {
329 var result : BoxesItem = [];
330 var font;
331
332 if (style === styleCache){
333 font = fontCache;
334 }else {
335
336
337 let fontPair = fontStyleTofont(style);
338
339 if (fontPair.path.match(/\.ttc$/)){
340 font = await fontkit.openSync(fontPair.path, fontPair.psName);
341 styleCache = style;
342 fontCache = font;
343
344 }
345 else{
346 font = await fontkit.openSync(fontPair.path);
347 styleCache = style;
348 fontCache = font;
349 }
350
351
352
353 }
354
355
356 if (!Array.isArray(element)){
357 var run = font.layout(element, undefined, undefined, undefined, "ltr");
358
359
360
361 for (var j=0;j<run.glyphs.length;j++){
362 let runGlyphsItem = run.glyphs[j];
363
364
365 let item : CharBox = {
366 x : null,
367 y : null,
368 textStyle : style,
369 direction : Direction.LTR,
370 width : (runGlyphsItem.advanceWidth)*(style.size)/1000 * 0.75, // in pt
371 height : (runGlyphsItem.bbox.maxY - runGlyphsItem.bbox.minY)*(style.size)/1000 * 0.75, // in pt
372 content : element[j],
373 minX : runGlyphsItem.bbox.minX,
374 maxX : runGlyphsItem.bbox.maxX,
375 minY : runGlyphsItem.bbox.minY,
376 maxY : runGlyphsItem.bbox.maxY
377 }
378
379 result.push(item);
380
381 }
382 return [result, styleCache, fontCache];
383
384
385
386 // break point of a line
387 }else if(element[0] == "bp"){
388
389
390 var beforeNewLine = (await calculateTextWidthHeightAux(element[1], style, styleCache, fontCache))[0];
391 if (Array.isArray(beforeNewLine)){
392 beforeNewLine = beforeNewLine.flat();
393 }
394
395 let afterNewLine = (await calculateTextWidthHeightAux(element[2], style, styleCache, fontCache))[0];
396 if (Array.isArray(afterNewLine)){
397 afterNewLine = afterNewLine.flat();
398 }
399
400 let breakPointNode : BreakPoint = {
401 original : beforeNewLine,
402 newLined : afterNewLine,
403 }
404
405
406 return [breakPointNode, styleCache, fontCache];
407 // hglue
408 }else if(element[0] == "hglue" && !Array.isArray(element[1])){
409 let hGlue : HGlue = {
410 isHorizonalGlue : true,
411 stretchFactor : parseFloat(element[1])}
412 return [hGlue, styleCache, fontCache];
413 }
414 // new line <br/>
415 else if(element[0] == "br"){
416 let brBoxItem = await calculateTextWidthHeightAux(["hglue", "10000"],
417 style, styleCache, fontCache);
418 // <br/>
419 let BR : BR = {
420 isBR : true,
421 original : brBoxItem[0],
422 newLined : brBoxItem[0]};
423 return [BR, styleCache, fontCache];
424 }
425 else{
426 return [await calculateTextWidthHeight(element, style), styleCache, fontCache];
427 }
428 }
429
430 /**
431 * put childrenBox inside VBox
432 */
433 export function putInVBox(childrenBox: Box[], parentBox: Box) : Box{
434 var voffset = Array(childrenBox.length).fill(0);
435
436 for (var i=0;i<childrenBox.length-1;i++){
437 voffset[i+1] = voffset[i] + childrenBox[i].height;
438
439 }
440 console.log("~", voffset);
441 for (var i=0; i<childrenBox.length; i++){
442 childrenBox[i] = applyVOffset(childrenBox[i], voffset[i]);
443 childrenBox[i].y += voffset[i];
444 }
445
446 parentBox.content = childrenBox;
447 return parentBox;
448 }
449
450 /**
451 * apply vertical offset to a box
452 * @param box the box to be applied
453 * @param voffset the vertical offset
454 * @returns applied box
455 */
456 export function applyVOffset(box : Box, voffset : number){
457 if(box.y !== null){
458 box.y += voffset;
459 }
460 if (Array.isArray(box.content)){
461 box.content = box.content.map((x)=>applyVOffset(x, voffset));
462 }
463 return box;
464 }
465
466 /**
467 * whole document-representing class
468 */
469 export class Clo{
470 /** storing the text string into the main frame */
471 mainStream : Array<string>;
472 /** array of preprocessor functions to preprocess the `mainStream` */
473 preprocessors : Array<Function>;
474 /** the attributes for the Clo */
475 attrs: {[index: string]:any} ; // a4 size(x,y)
476
477
478 constructor(){
479 this.preprocessors = [];
480 this.mainStream = [];
481 this.attrs = {
482 "page" : A4_IN_PX, // default for a4. in px of [x, y]
483 "defaultFrameStyle" : defaultFrameStyle, // defaultFrameStyle
484 "hyphenLanguage" : 'en' // hyphenated in the language (in ISO 639)
485 };
486
487
488
489 // register the precessor functions
490 this.preprocessorRegister(splitCJKV);
491 this.preprocessorRegister(hyphenForClo);
492 this.preprocessorRegister(twoReturnsToNewline);
493 this.preprocessorRegister(spacesToBreakpoint);
494 this.preprocessorRegister(filterEmptyString);
495 }
496
497 public setAttr(attr : string, val : any):void{
498 Object.assign(this.attrs, attr, val);
499 }
500
501 public getAttr(attr:string) : any{
502 if (Object.keys(this.attrs).length === 0){
503 return this.attrs[attr];
504 }else{
505 return undefined;
506 }
507
508 }
509
510 /**
511 * register a function of preprocessor
512 * @param f a function
513 */
514 public preprocessorRegister(f : Function){
515 this.preprocessors.push(f);
516 }
517
518 public async generatePdf(){
519
520 // preprocessed
521 var preprocessed = this.mainStream;
522 for (var i = 0; i<this.preprocessors.length; i++){
523 preprocessed = this.preprocessors[i](preprocessed, this);
524 }
525
526 // generate the width and height of the stream
527
528 let defaultFontStyle : TextStyle = this.attrs.defaultFrameStyle.textStyle;
529
530 // calculate the width and height of each chars
531 let calculated = await calculateTextWidthHeight(preprocessed, defaultFontStyle);
532
533 //
534 let paragraphized = this.paragraphize(calculated);
535
536
537 let breakLineAlgorithms = new breakLines.BreakLineAlgorithm();
538
539 let segmentedNodes = paragraphized.map((x)=>breakLineAlgorithms.segmentedNodes(x, this.attrs.defaultFrameStyle.width));
540
541 let segmentedNodesToBox = segmentedNodes.map((x)=>
542 this.segmentedNodesToFrameBoxAux(x, <FrameBox>this.attrs.defaultFrameStyle));
543
544 let boxWithParagraph = putInVBox(segmentedNodesToBox, this.attrs.defaultFrameStyle);
545
546 console.log(boxWithParagraph);
547
548 // fix the bug of main Frame x & y
549 if(boxWithParagraph.x !== null)
550 {boxWithParagraph.x *= 0.75}
551 if(boxWithParagraph.y !== null)
552 {boxWithParagraph.y *= 0.75}
553
554 let boxesFixed = this.fixenBoxesPosition(boxWithParagraph);
555
556
557 (<Box[]>boxesFixed.content).map((e)=>{console.log(e.y)});
558
559
560 // generate pdf
561 const doc = new PDFDocument({size: 'A4'});
562 doc.pipe(fs.createWriteStream('output.pdf'));
563 this.grid(doc);
564
565 let styleCache : any = {};
566 let fontPairCache : fontPathPSNamePair = {path : "", psName : ""};
567 await this.putText(doc, boxesFixed, <TextStyle>styleCache, fontPairCache);
568 // putChar
569 doc.end();
570
571
572 }
573
574 paragraphize(calculated : BoxesItem[]): BoxesItem[][]{
575 var res : BoxesItem[][] = [[]];
576 for (var i=0;i<calculated.length;i++){
577 if ("isBR" in <Box>(calculated[i])){
578 res[res.length-1] = res[res.length-1].concat(calculated[i]);
579 res.push([]);
580 }else{
581 res[res.length-1] = res[res.length-1].concat(calculated[i]);
582 }
583 }
584
585 res = res.filter((x)=>x.length !== 0);
586 return res;
587 }
588
589 async putText(doc : PDFKit.PDFDocument, box : Box, styleCache : TextStyle,
590 fontPairCache : fontPathPSNamePair):
591 Promise<[PDFKit.PDFDocument, TextStyle, fontPathPSNamePair]>{
592 var fontPair;
593
594
595 if (box.textStyle !== null){
596
597 if(box.textStyle == styleCache){
598 fontPair = fontPairCache;
599 }else{
600 fontPair = fontStyleTofont(box.textStyle);
601 styleCache = box.textStyle;
602 fontPairCache = fontPair;
603 let textColor = box.textStyle.color;
604
605 if (fontPair.path.match(/\.ttc$/g)){
606 doc
607 .fillColor(textColor !== undefined ? textColor : "#000000")
608 .font(fontPair.path, fontPair.psName)
609 .fontSize(box.textStyle.size * 0.75);}
610 else{
611 doc
612 .fillColor(textColor !== undefined ? textColor : "#000000")
613 .font(fontPair.path)
614 .fontSize(box.textStyle.size * 0.75); // 0.75 must added!
615 }
616 }
617
618 if (box.textStyle.color !== undefined){
619 doc.fill(box.textStyle.color);
620 }
621
622 if (Array.isArray(box.content)){
623 for (var k=0; k<box.content.length; k++){
624
625 let tmp = await this.putText(doc, box.content[k], styleCache, fontPairCache);
626 doc = tmp[0];
627 styleCache = tmp[1];
628 fontPairCache = tmp[2];
629 }
630 }else if (box.content !== null){
631 await doc.text(box.content,
632 (box.x!==null? box.x: undefined),
633 (box.y!==null? box.y: undefined));
634 }
635
636 }
637
638
639 return [doc, styleCache, fontPairCache];
640 };
641
642
643
644 private grid(doc: any) {
645 for (var j = 0; j < A4_IN_PX.width; j += 5) {
646 if (j % 50 == 0) {
647 doc.save().fill('#000000')
648 .fontSize(8).text(j.toString(), j*0.75, 50);
649
650 doc
651 .save()
652 .lineWidth(0.4)
653 .strokeColor("#dddddd")
654 .moveTo(j*0.75, 0)
655 .lineTo(j*0.75, 1000)
656 .stroke();
657 }
658
659 doc
660 .save()
661 .lineWidth(0.2)
662 .strokeColor("#dddddd")
663 .moveTo(j*0.75, 0)
664 .lineTo(j*0.75, 1000)
665 .stroke();
666 }
667
668 for (var i = 0; i < 1050; i += 5) {
669 if (i % 50 == 0) {
670 doc.save()
671 .fontSize(8).text(i.toString(), 50, i*0.75);
672
673 doc
674 .save()
675 .lineWidth(0.4)
676 .strokeColor("#bbbbbb")
677 .moveTo(0, i*0.75)
678 .lineTo(1000, i*0.75)
679 .stroke();
680 }
681 doc
682 .save()
683 .lineWidth(0.2)
684 .strokeColor("#bbbbbb")
685 .moveTo(0, i*0.75)
686 .lineTo(1000, i*0.75)
687 .stroke();
688 }
689 doc
690 .save()
691 .moveTo(0, 200)
692 .lineTo(1000, 200)
693 .fill('#FF3300');
694 }
695
696 /**
697 * make all the nest boxes's position fixed
698 * @param box the main boxes
699 * @returns the fixed boxes
700 */
701 fixenBoxesPosition(box : Box) : Box{
702 var currX : number = (box.x!==null?box.x:0); // current x
703 var currY : number =(box.y!==null?box.y:0); // current y
704 if (Array.isArray(box.content)){
705 for (var i=0; i<box.content.length; i++){
706 if (box.direction == Direction.LTR){
707 box.content[i].x = currX;
708 box.content[i].y = currY;
709 let elementWidth = box.content[i].width;
710 if(elementWidth !== null){
711 currX += elementWidth;
712 }
713
714 }
715 if (box.direction == Direction.TTB){
716 box.content[i].x = currX;
717 box.content[i].y = currY;
718 let elementHeight = box.content[i].height;
719 if(elementHeight !== null){
720 currY += elementHeight;
721 }
722
723 }
724 box.content[i] = this.fixenBoxesPosition(box.content[i]);
725 }
726 }
727
728 return box;
729 }
730
731 /**
732 * input a `segmentedNodes` and a layed `frame`, return a big `Box` that nodes is put in.
733 * @param segmentedNodes the segmentnodes to be input
734 * @param frame the frame to be layed out.
735 * @returns the big `Box`.
736 */
737 segmentedNodesToFrameBoxAux(segmentedNodes : BoxesItem[][], frame : FrameBox) : Box{
738 let baseLineskip = frame.baseLineskip;
739 let boxArrayEmpty : Box[] = [];
740 let bigBox : Box = {
741 x : (frame.x !==null? frame.x * 0.75 : null),
742 y : (frame.y !==null? frame.y * 0.75 : null),
743 textStyle : frame.textStyle,
744 direction : frame.direction,
745 width : frame.width,
746 height :frame.height,
747 content : boxArrayEmpty,
748 }
749
750 var bigBoxContent : Box[] = boxArrayEmpty;
751
752 let segmentedNodesFixed = segmentedNodes.map((x)=>this.removeBreakPoints
753 (x).flat());
754 let segmentedNodeUnglue = segmentedNodesFixed.map((x)=>this.removeGlue(x, frame).flat());
755
756 for (var i=0; i<segmentedNodeUnglue.length; i++){
757 var currentLineSkip = baseLineskip;
758 var glyphMaxHeight = this.getGlyphMaxHeight(segmentedNodesFixed[i]);
759 if (currentLineSkip === null || glyphMaxHeight >currentLineSkip ){
760 currentLineSkip = glyphMaxHeight;
761 }
762
763 var currentLineBox : Box = {
764 x : null,
765 y : null,
766 textStyle : defaultTextStyle,
767 direction : frame.directionInsideLine,
768 width : frame.width,
769 height : currentLineSkip,
770 content : <Box[]>segmentedNodeUnglue[i],
771 }
772
773 bigBoxContent.push(currentLineBox);
774
775 }
776
777 bigBox.content = bigBoxContent;
778 let bigBoxHeight = bigBoxContent.map((x)=>x.height).reduce((x,y)=>x+y, 0);
779 bigBox.height = bigBoxHeight;
780
781 return bigBox;
782 }
783
784 /**
785 * get the max height of the glyph`[a, b, c]`
786 * @param nodeLine the node line [a, b, c, ...]
787 * @returns
788 */
789 getGlyphMaxHeight(nodeLine : BoxesItem[]) : number{
790 let segmentedNodeLineHeight = nodeLine.map((x : BoxesItem)=>{if ("height" in x && x.height > 0.0){return x.height}else{return 0.0}});
791 let maxHeight = Math.max(...segmentedNodeLineHeight);
792 return maxHeight;
793 }
794
795 removeGlue(nodeLine : BoxesItem[], frame : FrameBox) : BoxesItem[]{
796 let breakLineAlgorithms = new breakLines.BreakLineAlgorithm();
797 let glueRemoved = nodeLine.filter((x)=>!breakLineAlgorithms.isHGlue(x));
798 let onlyGlue = nodeLine.filter((x)=>breakLineAlgorithms.isHGlue(x));
799 let sumStretchFactor = onlyGlue.map((x)=>{if("stretchFactor" in x){ return x.stretchFactor} else{return 0;}})
800 .reduce((acc, cur)=>acc+cur , 0);
801
802 let glueRemovedWidth = glueRemoved.map((x)=>{if("width" in x){ return x.width} else{return 0;}})
803 .reduce((acc, cur)=>acc+cur , 0);
804 let offset = frame.width * 0.75 - glueRemovedWidth;
805 var res = [];
806 for (var i=0; i<nodeLine.length; i++){
807 var ele = nodeLine[i];
808 if (breakLineAlgorithms.isHGlue(ele)){
809 let tmp : Box = {
810 x : null,
811 y : null,
812 textStyle : null,
813 direction : frame.directionInsideLine,
814 //width : 0, // ragged
815 width : ele.stretchFactor / sumStretchFactor * offset,
816 height : 0,
817 content : "",
818
819 }
820
821 res.push(tmp);
822 }else{
823 res.push(ele);
824 }
825 }
826
827 return res;
828 }
829
830 /**
831 * remove breakpoints
832 * @param boxitemline boxitem in a line with a breakpoint
833 * @returns boxitemline with break points removed
834 */
835 removeBreakPoints(boxitemline : BoxesItem[]) : BoxesItem[]{
836 var res : BoxesItem[] = [];
837 let breakLineAlgorithms = new breakLines.BreakLineAlgorithm();
838
839 for (var i = 0; i<boxitemline.length; i++){
840 let ele = boxitemline[i];
841 if (breakLineAlgorithms.isBreakPoint(ele)){
842 if (i == boxitemline.length-1){
843 res.push(ele.newLined);
844 }else{
845 res.push(ele.original);
846 }
847 }else{
848 res.push(ele);
849 }
850 }
851
852 return res;
853 }
854
855
856 }
857
858
859 /*
860 export let a = new Clo();
861 export default a; */