]> git.kianting.info Git - clo/blobdiff - src/libclo/index.ts
optimize the speed
[clo] / src / libclo / index.ts
index bb884fae1260a5fed6209a6895b835091f3a4607..f48f448d9bcabebaeaba7d0b21a60923bf4baf8b 100644 (file)
@@ -1,8 +1,12 @@
-import { isBoxedPrimitive, isKeyObject, isStringObject } from "util/types";
 import {tkTree} from "../parser";
-import {FontStyle, TextStyle, TextWeight, fontStyleTofont} from "../canva";
-import { JSDOM } from "jsdom";
+import {FontStyle, TextStyle, TextWeight, fontStyleTofont, fontPathPSNamePair} from "../canva";
 import * as fontkit from "fontkit";
+import * as breakLines from "./breakLines";
+const PDFDocument = require('pdfkit');
+import * as fs from "fs";
+import { Style } from "util";
+import { time } from "console";
+
 
 /**
  * TYPES
@@ -21,6 +25,21 @@ export enum Direction{
     BTT,
 }
 
+/**
+ * Horizonal glue.
+ * - stretchFactor : the stretch factor in float
+ */
+export interface HGlue{
+    stretchFactor: number
+}
+
+export interface BreakPoint{
+    original : BoxesItem,
+    newLined : BoxesItem  
+}
+
+export type BoxesItem = HGlue | Box | BreakPoint | BoxesItem[] ;
+
 /**
  * frame box is a subclass of box
  * - directionInsideLine : text direction inside a line
@@ -41,11 +60,11 @@ export interface CharBox extends Box{
 
 /**
  * a basic Box
- * - x :
- * - y : 
+ * - x : pt
+ * - y : pt
  * - textStyle :
  * - direction :
- * - width : x_advance
+ * - width : x_advance pt
  * - content :
  */
 export interface Box{
@@ -77,10 +96,10 @@ export const defaultFrameStyle : FrameBox = {
     direction : Direction.TTB,
     baseLineskip : ptToPx(15),
     textStyle : defaultTextStyle,
-    x : A4_IN_PX.width * 0.10,
-    y : A4_IN_PX.height * 0.10,
-    width : A4_IN_PX.width * 0.80,
-    height : A4_IN_PX.height * 0.80,
+    x : A4_IN_PX.width * 0.10 ,
+    y : A4_IN_PX.height * 0.10 ,
+    width : A4_IN_PX.width * 0.80  ,
+    height : A4_IN_PX.height * 0.80 ,
     content : null,
 };
 
@@ -197,7 +216,8 @@ export function spacesToBreakpoint(arr : tkTree, clo : Clo) : tkTree{
     for (let i = 0; i < arr.length; i++){
         var item = arr[i];
         if (!Array.isArray(item) && item.match(spacePattern)){
-            result.push([ 'bp', item, "" ]); // push a newline command to the result `tkTree`
+            // push a breakpoint command to the result `tkTree`
+            result.push([ 'bp', [["hglue", "0.1"], item] , "" ]); 
         }
         else{
             result.push(item);
@@ -261,19 +281,26 @@ export function hyphenTkTree(arr : tkTree, lang: string) : tkTree{
     return result;
 }
 
+
+
 /**
  * calculate the text width and Height with a given `TextStyle` 
  * @param preprocessed 
  * @param defaultFontStyle 
  */
-export async function calculateTextWidthHeight(element : tkTree, style : TextStyle): Promise<any> {
+export async function calculateTextWidthHeight(element : tkTree, style : TextStyle): Promise<BoxesItem[]> {
     var res = [];
+    var styleCache = {};
+    var fontCache = {};
     
     for (var i=0; i<element.length; i++){
-        res.push(await calculateTextWidthHeightAux(element[i], style));
+        let item = await calculateTextWidthHeightAux(element[i], style, <TextStyle>styleCache, <fontkit.Font>fontCache);
+        styleCache = item[1];
+        fontCache = item[2];
+        res.push(item[0]);
     }
 
-    console.log(res);
+    res = res.flat();
 
     return res;
 }
@@ -284,18 +311,37 @@ export async function calculateTextWidthHeight(element : tkTree, style : TextSty
  * @param preprocessed 
  * @param defaultFontStyle 
  */
-export async function calculateTextWidthHeightAux(element : tkTree, style : TextStyle): Promise<any> {
-    var result : any = [];
-    
+export async function calculateTextWidthHeightAux(element : tkTree,
+                                                    style : TextStyle,
+                                                    styleCache : TextStyle,
+                                                    fontCache :  fontkit.Font): Promise<[BoxesItem, TextStyle, fontkit.Font] > {
+    var result : BoxesItem = [];
+    var font;
+
+    if (style === styleCache){
+        font = fontCache;
+    }else {
 
 
     let fontPair = fontStyleTofont(style);
+
     if (fontPair.path.match(/\.ttc$/)){
-        var font = await fontkit.openSync(fontPair.path, fontPair.psName);
+        font = await fontkit.openSync(fontPair.path, fontPair.psName);
+        styleCache = style;
+        fontCache = font;
+
     }
     else{
-        var font = await fontkit.openSync(fontPair.path);
+        font = await fontkit.openSync(fontPair.path);
+        styleCache = style;
+        fontCache = font;
     }
+
+    
+
+    }
+
+
     if (!Array.isArray(element)){
         var run = font.layout(element, undefined, undefined, undefined, "ltr");
 
@@ -310,8 +356,8 @@ export async function calculateTextWidthHeightAux(element : tkTree, style : Text
                 y : null,
                 textStyle : style,
                 direction : Direction.LTR,
-                width : (runGlyphsItem.advanceWidth)*(style.size)/1000,
-                height : (runGlyphsItem.bbox.maxY - runGlyphsItem.bbox.minY)*(style.size)/1000,
+                width : (runGlyphsItem.advanceWidth)*(style.size)/1000 * 0.75, // in pt
+                height : (runGlyphsItem.bbox.maxY - runGlyphsItem.bbox.minY)*(style.size)/1000 * 0.75, // in pt
                 content : element[j],
                 minX : runGlyphsItem.bbox.minX,
                 maxX : runGlyphsItem.bbox.maxX,
@@ -322,18 +368,36 @@ export async function calculateTextWidthHeightAux(element : tkTree, style : Text
             result.push(item);
 
         }
-    return result;
+    return [result, styleCache, fontCache];
 
 
         
 
     }else if(element[0] == "bp"){
-        let beforeNewLine = await calculateTextWidthHeightAux(element[1], style);
-        let afterNewLine = await calculateTextWidthHeightAux(element[2], style);
 
-        return ["bp",  beforeNewLine,  afterNewLine];
-    }else{
-        return calculateTextWidthHeight(element[1], style);
+
+        var beforeNewLine = (await calculateTextWidthHeightAux(element[1], style, styleCache, fontCache))[0];
+        if (Array.isArray(beforeNewLine)){
+            beforeNewLine = beforeNewLine.flat();
+        }
+
+        let afterNewLine = (await calculateTextWidthHeightAux(element[2], style, styleCache, fontCache))[0];
+        if (Array.isArray(afterNewLine)){
+            afterNewLine = afterNewLine.flat();
+        }
+
+        let breakPointNode : BreakPoint = {
+            original : beforeNewLine,
+            newLined : afterNewLine,
+        }
+
+        return [breakPointNode, styleCache, fontCache];
+    }else if(element[0] == "hglue" && !Array.isArray(element[1])){
+        let hGlue : HGlue = {stretchFactor : parseFloat(element[1])}
+        return [hGlue, styleCache, fontCache];
+    }
+    else{
+        return [await calculateTextWidthHeight(element, style), styleCache, fontCache];
     }
 }
 
@@ -392,24 +456,315 @@ export class Clo{
         this.preprocessors.push(f);
     }
 
-    public generatePdf(){
+    public async generatePdf(){
+
         // preprocessed
         var preprocessed = this.mainStream;
         for (var i = 0; i<this.preprocessors.length; i++){
             preprocessed = this.preprocessors[i](preprocessed, this);
         }
+
         // generate the width and height of the stream
 
-        let defaultFontStyle : TextStyle = this.attrs["defaultFrameStyle"].textStyle;
-        calculateTextWidthHeight(preprocessed, defaultFontStyle);
+        let defaultFontStyle : TextStyle = this.attrs.defaultFrameStyle.textStyle;
+
+
+        let a = await calculateTextWidthHeight(preprocessed, defaultFontStyle);
+
+        let breakLineAlgorithms = new breakLines.BreakLineAlgorithm();
+
+        let segmentedNodes = breakLineAlgorithms.segmentedNodes(a, this.attrs.defaultFrameStyle.width);
+
+        let segmentedNodesToBox =
+            this.segmentedNodesToFrameBox(segmentedNodes, <FrameBox>this.attrs.defaultFrameStyle);
+
+
+        let boxesFixed = this.fixenBoxesPosition(segmentedNodesToBox);
+
+        
+
+
+        // generate pdf
+        const doc = new PDFDocument({size: 'A4'});
+        doc.pipe(fs.createWriteStream('output.pdf'));
+        this.grid(doc);
+
+        let styleCache : any = {};
+        let fontPairCache : fontPathPSNamePair = {path : "", psName : ""};
+        await this.putText(doc, boxesFixed, <TextStyle>styleCache, fontPairCache);
+        // putChar
+        doc.end();
+
 
-        // TODO
-        console.log(preprocessed);
+    }
+
+    async putText(doc : PDFKit.PDFDocument, box : Box, styleCache : TextStyle,
+        fontPairCache : fontPathPSNamePair):
+        Promise<[PDFKit.PDFDocument, TextStyle, fontPathPSNamePair]>{
+            var fontPair;
+        
+    
+        if (box.textStyle !== null){
+            
+            if(box.textStyle == styleCache){
+                fontPair = fontPairCache;
+            }else{
+                fontPair = fontStyleTofont(box.textStyle);
+                styleCache = box.textStyle;
+                fontPairCache = fontPair;
+
+                if (fontPair.path.match(/\.ttc$/g)){
+                    doc
+                    .font(fontPair.path, fontPair.psName)
+                    .fontSize(box.textStyle.size * 0.75);}
+                else{
+                    doc
+                    .font(fontPair.path)
+                    .fontSize(box.textStyle.size * 0.75); // 0.75 must added!  
+                }
+        }
+        
+            if (box.textStyle.color !== undefined){
+                doc.fill(box.textStyle.color);
+            }
+            
+            if (Array.isArray(box.content)){
+                for (var k=0; k<box.content.length; k++){
+
+                    let tmp = await this.putText(doc, box.content[k], styleCache, fontPairCache);
+                    doc = tmp[0];
+                    styleCache = tmp[1];
+                    fontPairCache = tmp[2];
+                }
+            }else if (box.content !== null){
+                await doc.text(box.content,
+                    (box.x!==null? box.x: undefined),
+                    (box.y!==null? box.y: undefined));
+            }
+        
+        }
+
+
+        return [doc, styleCache, fontPairCache];
+    };
+
+
+
+    private grid(doc: any) {
+        for (var j = 0; j <  A4_IN_PX.width; j += 5) {
+            if (j % 50 == 0) {
+                doc.save().fill('#000000')
+                    .fontSize(8).text(j.toString(), j*0.75, 50);
+
+                doc
+                    .save()
+                    .lineWidth(0.4)
+                    .strokeColor("#dddddd")
+                    .moveTo(j*0.75, 0)
+                    .lineTo(j*0.75, 1000)
+                    .stroke();
+            }
+
+            doc
+                .save()
+                .lineWidth(0.2)
+                .strokeColor("#dddddd")
+                .moveTo(j*0.75, 0)
+                .lineTo(j*0.75, 1000)
+                .stroke();
+        }
+
+        for (var i = 0; i < 1050; i += 5) {
+            if (i % 50 == 0) {
+                doc.save()
+                    .fontSize(8).text(i.toString(), 50, i*0.75);
+
+                doc
+                    .save()
+                    .lineWidth(0.4)
+                    .strokeColor("#bbbbbb")
+                    .moveTo(0, i*0.75)
+                    .lineTo(1000, i*0.75)
+                    .stroke();
+            }
+            doc
+                .save()
+                .lineWidth(0.2)
+                .strokeColor("#bbbbbb")
+                .moveTo(0, i*0.75)
+                .lineTo(1000, i*0.75)
+                .stroke();
+        }
+        doc
+            .save()
+            .moveTo(0, 200)
+            .lineTo(1000, 200)
+            .fill('#FF3300');
+    }
+
+    /**
+     * make all the nest boxes's position fixed
+     * @param box the main boxes
+     * @returns the fixed boxes
+     */
+    fixenBoxesPosition(box : Box) : Box{
+        var currX : number = (box.x!==null?box.x:0); // current x
+        var currY : number =(box.y!==null?box.y:0); // current y
+        if (Array.isArray(box.content)){
+            for (var i=0; i<box.content.length; i++){
+                if (box.direction == Direction.LTR){
+                    box.content[i].x = currX;
+                    box.content[i].y = currY;
+                    let elementWidth = box.content[i].width;
+                    if(elementWidth !== null){
+                        currX += elementWidth;
+                    }
+
+                }
+                if (box.direction == Direction.TTB){
+                    box.content[i].x = currX;
+                    box.content[i].y = currY;
+                    let elementHeight = box.content[i].height;
+                    if(elementHeight !== null){
+                        currY += elementHeight;
+                    }
+
+                }
+
+
+                box.content[i] = this.fixenBoxesPosition(box.content[i]);
+            }
+        }
+
+        return box;
+    }
+
+    /**
+     * input a `segmentedNodes` and a layed `frame`, return a big `Box` that nodes is put in.
+     * @param segmentedNodes the segmentnodes to be input
+     * @param frame the frame to be layed out.
+     * @returns the big `Box`.
+     */
+    segmentedNodesToFrameBox(segmentedNodes : BoxesItem[][], frame : FrameBox) : Box{
+        let baseLineskip = frame.baseLineskip;
+        let boxArrayEmpty  : Box[] = [];
+        let bigBox : Box = {
+            x : (frame.x !==null? frame.x * 0.75 : null),
+            y : (frame.y !==null? frame.y * 0.75 : null),
+            textStyle :  frame.textStyle,
+            direction : frame.direction,
+            width : frame.width,
+            height :frame.height,
+            content : boxArrayEmpty,
+        }
+
+        var bigBoxContent : Box[] = boxArrayEmpty;
+
+        let segmentedNodesFixed = segmentedNodes.map((x)=>this.removeBreakPoints
+(x).flat());
+        let segmentedNodeUnglue = segmentedNodesFixed.map((x)=>this.removeGlue(x, frame).flat());
+
+        for (var i=0; i<segmentedNodeUnglue.length; i++){
+            var currentLineSkip = baseLineskip;
+            var glyphMaxHeight = this.getGlyphMaxHeight(segmentedNodesFixed[i]);
+            if (currentLineSkip === null || glyphMaxHeight >currentLineSkip ){
+                currentLineSkip = glyphMaxHeight;
+            }
+
+            var currentLineBox : Box = {
+                x : null,
+                y : null,
+                textStyle : defaultTextStyle,
+                direction : frame.directionInsideLine,
+                width :  frame.width,
+                height : currentLineSkip,
+                content : <Box[]>segmentedNodeUnglue[i],
+            }
+
+            bigBoxContent.push(currentLineBox);
+
+        }
+
+        bigBox.content = bigBoxContent;
+
+        return bigBox;
+    }
+
+    /**
+     * get the max height of the glyph`[a, b, c]`
+     * @param nodeLine the node line [a, b, c, ...]
+     * @returns 
+     */
+    getGlyphMaxHeight(nodeLine : BoxesItem[]) : number{
+        let segmentedNodeLineHeight = nodeLine.map((x : BoxesItem)=>{if ("height" in x && x.height > 0.0){return x.height}else{return 0.0}});
+        let maxHeight = Math.max(...segmentedNodeLineHeight);
+        return maxHeight;
+    }
+
+    removeGlue(nodeLine : BoxesItem[], frame : FrameBox) : BoxesItem[]{
+        let breakLineAlgorithms = new breakLines.BreakLineAlgorithm();
+        let glueRemoved = nodeLine.filter((x)=>!breakLineAlgorithms.isHGlue(x));
+        let onlyGlue = nodeLine.filter((x)=>breakLineAlgorithms.isHGlue(x));
+        let sumStretchFactor = onlyGlue.map((x)=>{if("stretchFactor" in x){ return x.stretchFactor} else{return 0;}})
+            .reduce((acc, cur)=>acc+cur , 0);
+
+        let glueRemovedWidth = glueRemoved.map((x)=>{if("width" in x){ return x.width} else{return 0;}})
+            .reduce((acc, cur)=>acc+cur , 0);
+        let offset = frame.width * 0.75 - glueRemovedWidth;
+        var res = [];
+        for (var i=0; i<nodeLine.length; i++){
+            var ele = nodeLine[i];
+            if (breakLineAlgorithms.isHGlue(ele)){
+                let tmp : Box = {
+                    x : null,
+                    y : null,
+                    textStyle : null,
+                    direction : frame.directionInsideLine,
+                    //width : 0, // ragged
+                    width : ele.stretchFactor / sumStretchFactor * offset,
+                    height : 0,
+                    content : "",
+
+                }
+
+                res.push(tmp);
+            }else{
+                res.push(ele);
+            }
+        }
+
+        return res;
+    }
+
+    /**
+     * remove breakpoints
+     * @param boxitemline boxitem in a line with a breakpoint
+     * @returns boxitemline with break points removed
+     */
+    removeBreakPoints(boxitemline : BoxesItem[]) : BoxesItem[]{
+        var res : BoxesItem[]  = [];
+        let breakLineAlgorithms = new breakLines.BreakLineAlgorithm();
+
+        for (var i = 0; i<boxitemline.length; i++){
+            let ele = boxitemline[i];
+            if (breakLineAlgorithms.isBreakPoint(ele)){
+                if (i == boxitemline.length-1){
+                    res.push(ele.newLined);
+                }else{
+                    res.push(ele.original);
+                }
+            }else{
+                res.push(ele);
+            }
+        }
+
+        return res;
     }
 
     
 }
 
+
 /*
 export let a = new Clo();
 export default a; */
\ No newline at end of file