V2.js

코드가 올려져 있긴 하지만 참고로...

https://github.com/projectBS/S65/blob/master/canvas/V2.js

const V = (()=>{
    const readOnly = (target, v)=>{
        for(let k in v) v[k] = {value:v[k]};
        Object.defineProperties(target, v);
    };
    const error = msg=>throw msg;
    const N = (factory,cnt)=>Array.from('0'.repeat(cnt)).map(_=>factory());


    const [offset, size, draw, bound, start, end, reset, image, rect, text] = N(Symbol, 10);

    const Paint = (([RESET, BOUND, START, END, IMAGE, RECT, TEXT])=>{
        const Paint = class{
            constructor(width, height){
                this[size](width, height);
            }
            [size](width, height){
                define(this, {width, height});
            }
            [bound]({x, y, width, height}){this[BOUND](x, y, width, height);}
            reset(){this[RESET]();}

            [start](display){this[START](display);}
            [end](display){this[END](display);}
            [image](display){this[IMAGE](display);}
            [rect](display){this[RECT](display);}
            [text](display){this[TEXT](display);}
        };
        return Object.assign(Paint, {RESET, BOUND, START, END, IMAGE, RECT, TEXT});
    })(N(Symbol, 7));

    const Stage = class{
        constructor(paint){
            if(paint instanceof Paint) readOnly(this, {paint, root:new DisplayContainer()});
            else error();
        }

        addChild(display){this.root.addChild(display);}
        removeChild(display){this.root.removeChild(display);}
        getChildAt(index){this.root.getChildAt(index);}
        getChildById(id){this.root.getChildById(id);}

        render(){
            let {width, height} = this.root[Display.MEASURE](this.paint.width, this.paint.height);
            this.root[size](width, height);
            this.root[offset](0, 0);
            this.paint[reset]();
            this.root[Display.DRAW](this.paint);
        }
    };

    const Display = (([event, listener, boundRect, DRAW, MEASURE, OFFSET])=>{
        let uuid = 0;
        const bound = {};
        const Style = class{
            constructor(isBlock){
                this.display = isBlock ? 'block' : 'inline';
            }
        };
        const Event =  (stop=>class{
            constructor(target){
                readOnly(this, {target});
            }
            init(type){
                this.type = type;
                this[stop] = false;
                return this;
            }
            stopPropagation(){
                this[stop] = true;
            }
            get isStopPropagation(){
                return this[stop];
            }
        })(Symbol());

        const Display = class{
            constructor(isBlock){
                define(this, {
                    id:uuid++,
                    [event]:new Event(this),
                    [listener]:{},
                    [boundRect]:{},
                    style:new Style(isBlock)
                });
            }
            dispatch(type){
                let listeners;
                if(listeners = this[listener][type]){
                    let e = this[event].init(type);
                    for(let listener of listeners){
                        listener(ev);
                        if(ev.isStopPropagation) return;
                    }
                }
            }
            addListener(type, callback){
                const target = (this[listener][type] || (this[listener][type] = new Set()));
                target.add(callback);
            }
            removeListener(type, callback = null){
                const target = this[listener][type];
                if(!target) return;
                if(listener) target.delete(callback);
                else target.clear();
            }

            [offset](x, y){this[boundRect].x = x, this[boundRect].y = y;}
            [size](width, height){this[boundRect].width = width, this[boundRect].height = height;}

            get boundRect(){
                const rect = this[boundRect];
                if(!this.style._margin) return rect;
                bound.x = rect.x + this.style._margin[3];
                bound.y = rect.y + this.style._margin[0];
                bound.width = rect.width;
                bound.height = rect.height;
                return bound;
            }

            [draw](paint){
                paint[start](this);
                this[DRAW](paint);
                paint[end](this);
            }
            [DRAW](paint){}
        };
        return Object.assign(Display, {DRAW, MEASURE, OFFSET});
    })(N(Symbol, 6));

    const DisplayContainer = ([children, ids]=>class extends Display{
        constructor(isBlock){
            super(isBlock);
            readOnly(this, {
                [children]:new Set(),
                [ids]:new Map()
            });
        }
        addChild(display){
            if(!(display instanceof Display)) return;
            this.removeChild(display);

            this[children].add(display);
            this[ids][display.id] = display;

        }
        removeChild(display){
            if(!(display instanceof Display) || !this[ids][display.id]) return;
            this[ids].delete(display.id);
            this[children].delete(display);
        }
        getChildAt(index){
            if(this[children].size <= index) return null;
            let idx = 0;
            return  ? null : this[children].forEach((v1, v2, set)=>idx++ == index ? set.delete(v1) : 0);
        }
        getChildById(id){
            return this[ids][display.id] || null;
        }
        [draw](paint){
            super[DRAW](paint);
            for(let child of this[children]) child[draw](paint);
        }
        [MEASURE](parentWidth, parentHeight){
            let totalH = 0, lineW = 0, lineH = 0;
            Css.size(this.style, parentWidth, parentHeight);
            for(let child of this[CHILDREN]){
                let {width, height} = child[Display.MEASURE](parentWidth, 0);
                child.setSize(width, height);
                let [marginT, marginR, marginB, marginL] = Css.margin(child.style);
                width += marginL + marginR;
                height += marginB + marginT;
                if(child.style.display == 'block'){
                    if(lineH){
                        totalH += lineH;
                        lineH = lineW = 0;
                    }
                    child.setOffset(0, totalH);
                    totalH += height;
                }else{
                    let offsetX = lineW;
                    if(lineW + width > parentWidth){
                        if(lineH){
                            totalH += lineH;
                            lineH = lineW = 0;
                        }
                        lineW = width;
                        lineH = height;
                    }else{
                        lineW += width;
                        if(lineH < height) lineH = height;
                    }
                    child.setOffset(offsetX, totalH);
                }
            }
            if(lineH) totalH += lineH;
            switch(this.style.textAlign){
            case'right':
                let i = this[CHILDREN].length, j = parentWidth;
                while(i--){
                    let c = this[CHILDREN][i], rect = c[BOUNDRECT];
                    rect.x = j -= rect.width + c.style._margin[1] + c.style._margin[3];
                }
                break;
            case'justify':
            }
            return {width:parentWidth, height:totalH};
        }
    })(N(Symbol, 2));

    return v = {
        Event, Display, DisplayContainer, Stage,
        spread:()=>{
            for(let k in v) window[k] = v[k];
        }
    };
})();