X-Git-Url: https://git.kianting.info/?a=blobdiff_plain;f=src%2Flibclo%2Findex.js;h=fe92ce14683dae9d90116a36ae12de9cf70648ff;hb=714bbb4a64531268d17ca8f7ae60800ec2046a27;hp=944aba2b1304c564dc166874f12915ca0acd2834;hpb=c3dc58d74afa6b298d84bad90d63c027a32a954a;p=clo diff --git a/src/libclo/index.js b/src/libclo/index.js index 944aba2..fe92ce1 100644 --- a/src/libclo/index.js +++ b/src/libclo/index.js @@ -1,8 +1,43 @@ "use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; Object.defineProperty(exports, "__esModule", { value: true }); -exports.Clo = exports.calculateTextWidthHeight = exports.hyphenTkTree = exports.filterEmptyString = exports.spacesToBreakpoint = exports.hyphenForClo = exports.splitCJKV = exports.twoReturnsToNewline = exports.ptToPx = exports.cjkvRegexPattern = exports.cjkvBlocksInRegex = exports.defaultFrameStyle = exports.defaultTextStyle = exports.A4_IN_PX = exports.Direction = void 0; +exports.Clo = exports.applyVOffset = exports.putInVBox = exports.calculateTextWidthHeightAux = exports.calculateTextWidthHeight = exports.hyphenTkTree = exports.filterEmptyString = exports.spacesToBreakpoint = exports.hyphenForClo = exports.splitCJKV = exports.twoReturnsToNewline = exports.ptToPx = exports.cjkvRegexPattern = exports.cjkvBlocksInRegex = exports.defaultFrameStyle = exports.defaultTextStyle = exports.A4_IN_PX = exports.Direction = void 0; const canva_1 = require("../canva"); -const jsdom_1 = require("jsdom"); +const fontkit = __importStar(require("fontkit")); +const breakLines = __importStar(require("./breakLines")); +const PDFDocument = require('pdfkit'); +const memfs_1 = require("memfs"); /** * TYPES */ @@ -25,7 +60,7 @@ var Direction; exports.A4_IN_PX = { "width": 793.7, "height": 1122.5 }; exports.defaultTextStyle = { - family: "FreeSerif", + family: "Noto Sans CJK TC", size: ptToPx(12), textWeight: canva_1.TextWeight.REGULAR, fontStyle: canva_1.FontStyle.ITALIC, @@ -68,7 +103,7 @@ exports.ptToPx = ptToPx; * REGISTER PART */ /** - * convert '\n\n' to newline command ["nl"] + * convert '\n\n' to new paragraph command ["br"] * @param arr the input `tkTree` * @param clo the `Clo` object * @returns the input tktree @@ -88,7 +123,7 @@ function twoReturnsToNewline(arr, clo) { for (let j = 0; j < middle.length; j++) { var item = middle[j]; if (!Array.isArray(item) && item == "\n\n") { - result.push(["nl"]); // push a newline command to the result `tkTree` + result.push(["br"]); // push a newline command to the result `tkTree` } else { result.push(middle[j]); @@ -145,7 +180,8 @@ function spacesToBreakpoint(arr, clo) { 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); @@ -206,38 +242,142 @@ exports.hyphenTkTree = hyphenTkTree; * @param preprocessed * @param defaultFontStyle */ -function calculateTextWidthHeight(preprocessed, style) { - var dom = new jsdom_1.JSDOM(` - `); - try { - let canvas = dom.window.document.getElementById("canvas"); - console.log(canvas); - /*if (!(canvas instanceof HTMLElement)){ - throw new Error('the in the jsdom\'s DOM is not found.'); - - }*/ - let context = canvas.getContext("2d"); - console.log(context); - if (context == null) { - throw new Error('`canvas.getContext("2d");` can\'t be executed.'); +function calculateTextWidthHeight(element, style) { + return __awaiter(this, void 0, void 0, function* () { + var res = []; + var styleCache = {}; + var fontCache = {}; + for (var i = 0; i < element.length; i++) { + let item = yield calculateTextWidthHeightAux(element[i], style, styleCache, fontCache); + styleCache = item[1]; + fontCache = item[2]; + res.push(item[0]); + } + res = res.flat(); + return res; + }); +} +exports.calculateTextWidthHeight = calculateTextWidthHeight; +/** + * calculate the text width and Height with a given `TextStyle` + * @param preprocessed + * @param defaultFontStyle + */ +function calculateTextWidthHeightAux(element, style, styleCache, fontCache) { + return __awaiter(this, void 0, void 0, function* () { + var result = []; + var font; + if (style === styleCache) { + font = fontCache; + } + else { + let fontPair = (0, canva_1.fontStyleTofont)(style); + if (fontPair.path.match(/\.ttc$/)) { + font = yield fontkit.openSync(fontPair.path, fontPair.psName); + styleCache = style; + fontCache = font; + } + else { + font = yield fontkit.openSync(fontPair.path); + styleCache = style; + fontCache = font; + } } - context.font = `normal normal 10pt ${style.family}`; - console.log(context.font); - let txt = `Hello john`; - console.log(txt); - let measured = context.measureText(txt); - let width = measured.width; - let height = measured.actualBoundingBoxAscent; - let depth = measured.actualBoundingBoxDescent; - console.log("width: " + width); - console.log("height: " + height); - console.log("depth: " + depth); + if (!Array.isArray(element)) { + var run = font.layout(element, undefined, undefined, undefined, "ltr"); + for (var j = 0; j < run.glyphs.length; j++) { + let runGlyphsItem = run.glyphs[j]; + let item = { + x: null, + y: null, + textStyle: style, + direction: Direction.LTR, + width: (runGlyphsItem.advanceWidth) * (style.size) / 1000 * 0.75, + height: (runGlyphsItem.bbox.maxY - runGlyphsItem.bbox.minY) * (style.size) / 1000 * 0.75, + content: element[j], + minX: runGlyphsItem.bbox.minX, + maxX: runGlyphsItem.bbox.maxX, + minY: runGlyphsItem.bbox.minY, + maxY: runGlyphsItem.bbox.maxY + }; + result.push(item); + } + return [result, styleCache, fontCache]; + // break point of a line + } + else if (element[0] == "bp") { + var beforeNewLine = (yield calculateTextWidthHeightAux(element[1], style, styleCache, fontCache))[0]; + if (Array.isArray(beforeNewLine)) { + beforeNewLine = beforeNewLine.flat(); + } + let afterNewLine = (yield calculateTextWidthHeightAux(element[2], style, styleCache, fontCache))[0]; + if (Array.isArray(afterNewLine)) { + afterNewLine = afterNewLine.flat(); + } + let breakPointNode = { + original: beforeNewLine, + newLined: afterNewLine, + }; + return [breakPointNode, styleCache, fontCache]; + // hglue + } + else if (element[0] == "hglue" && !Array.isArray(element[1])) { + let hGlue = { + isHorizonalGlue: true, + stretchFactor: parseFloat(element[1]) + }; + return [hGlue, styleCache, fontCache]; + } + // new line
+ else if (element[0] == "br") { + let brBoxItem = yield calculateTextWidthHeightAux(["hglue", "10000"], style, styleCache, fontCache); + //
+ let BR = { + isBR: true, + original: brBoxItem[0], + newLined: brBoxItem[0] + }; + return [BR, styleCache, fontCache]; + } + else { + return [yield calculateTextWidthHeight(element, style), styleCache, fontCache]; + } + }); +} +exports.calculateTextWidthHeightAux = calculateTextWidthHeightAux; +/** + * put childrenBox inside VBox + */ +function putInVBox(childrenBox, parentBox) { + var voffset = Array(childrenBox.length).fill(0); + for (var i = 0; i < childrenBox.length - 1; i++) { + voffset[i + 1] = voffset[i] + childrenBox[i].height; } - catch (error) { - console.log("Exception " + error); + console.log("~", voffset); + for (var i = 0; i < childrenBox.length; i++) { + childrenBox[i] = applyVOffset(childrenBox[i], voffset[i]); + childrenBox[i].y += voffset[i]; } + parentBox.content = childrenBox; + return parentBox; } -exports.calculateTextWidthHeight = calculateTextWidthHeight; +exports.putInVBox = putInVBox; +/** + * apply vertical offset to a box + * @param box the box to be applied + * @param voffset the vertical offset + * @returns applied box + */ +function applyVOffset(box, voffset) { + if (box.y !== null) { + box.y += voffset; + } + if (Array.isArray(box.content)) { + box.content = box.content.map((x) => applyVOffset(x, voffset)); + } + return box; +} +exports.applyVOffset = applyVOffset; /** * whole document-representing class */ @@ -276,16 +416,302 @@ class Clo { this.preprocessors.push(f); } generatePdf() { - // preprocessed - var preprocessed = this.mainStream; - for (var i = 0; i < this.preprocessors.length; i++) { - preprocessed = this.preprocessors[i](preprocessed, this); + return __awaiter(this, void 0, void 0, function* () { + // 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 = this.attrs.defaultFrameStyle.textStyle; + // calculate the width and height of each chars + let calculated = yield calculateTextWidthHeight(preprocessed, defaultFontStyle); + // + let paragraphized = this.paragraphize(calculated); + let breakLineAlgorithms = new breakLines.BreakLineAlgorithm(); + let segmentedNodes = paragraphized.map((x) => breakLineAlgorithms.segmentedNodes(x, this.attrs.defaultFrameStyle.width)); + let segmentedNodesToBox = segmentedNodes.map((x) => this.segmentedNodesToFrameBoxAux(x, this.attrs.defaultFrameStyle)); + let boxWithParagraph = putInVBox(segmentedNodesToBox, this.attrs.defaultFrameStyle); + console.log(boxWithParagraph); + // fix the bug of main Frame x & y + if (boxWithParagraph.x !== null) { + boxWithParagraph.x *= 0.75; + } + if (boxWithParagraph.y !== null) { + boxWithParagraph.y *= 0.75; + } + let boxesFixed = this.fixenBoxesPosition(boxWithParagraph); + boxesFixed.content.map((e) => { console.log(e.y); }); + // generate pdf + const doc = new PDFDocument({ size: 'A4' }); + // let fsMemory = memfs(); + doc.pipe(memfs_1.vol.createWriteStream('output.pdf')); + this.grid(doc); + let styleCache = {}; + let fontPairCache = { path: "", psName: "" }; + yield this.putText(doc, boxesFixed, styleCache, fontPairCache); + // putChar + doc.end(); + }); + } + paragraphize(calculated) { + var res = [[]]; + for (var i = 0; i < calculated.length; i++) { + if ("isBR" in (calculated[i])) { + res[res.length - 1] = res[res.length - 1].concat(calculated[i]); + res.push([]); + } + else { + res[res.length - 1] = res[res.length - 1].concat(calculated[i]); + } + } + res = res.filter((x) => x.length !== 0); + return res; + } + putText(doc, box, styleCache, fontPairCache) { + return __awaiter(this, void 0, void 0, function* () { + var fontPair; + if (box.textStyle !== null) { + if (box.textStyle == styleCache) { + fontPair = fontPairCache; + } + else { + fontPair = (0, canva_1.fontStyleTofont)(box.textStyle); + styleCache = box.textStyle; + fontPairCache = fontPair; + let textColor = box.textStyle.color; + if (fontPair.path.match(/\.ttc$/g)) { + doc + .fillColor(textColor !== undefined ? textColor : "#000000") + .font(fontPair.path, fontPair.psName) + .fontSize(box.textStyle.size * 0.75); + } + else { + doc + .fillColor(textColor !== undefined ? textColor : "#000000") + .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 = yield this.putText(doc, box.content[k], styleCache, fontPairCache); + doc = tmp[0]; + styleCache = tmp[1]; + fontPairCache = tmp[2]; + } + } + else if (box.content !== null) { + yield doc.text(box.content, (box.x !== null ? box.x : undefined), (box.y !== null ? box.y : undefined)); + } + } + return [doc, styleCache, fontPairCache]; + }); + } + ; + grid(doc) { + for (var j = 0; j < exports.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) { + var currX = (box.x !== null ? box.x : 0); // current x + var currY = (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`. + */ + segmentedNodesToFrameBoxAux(segmentedNodes, frame) { + let baseLineskip = frame.baseLineskip; + let boxArrayEmpty = []; + let bigBox = { + 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 = 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 = { + x: null, + y: null, + textStyle: exports.defaultTextStyle, + direction: frame.directionInsideLine, + width: frame.width, + height: currentLineSkip, + content: segmentedNodeUnglue[i], + }; + bigBoxContent.push(currentLineBox); + } + bigBox.content = bigBoxContent; + let bigBoxHeight = bigBoxContent.map((x) => x.height).reduce((x, y) => x + y, 0); + bigBox.height = bigBoxHeight; + return bigBox; + } + /** + * get the max height of the glyph`[a, b, c]` + * @param nodeLine the node line [a, b, c, ...] + * @returns + */ + getGlyphMaxHeight(nodeLine) { + let segmentedNodeLineHeight = nodeLine.map((x) => { if ("height" in x && x.height > 0.0) { + return x.height; + } + else { + return 0.0; + } }); + let maxHeight = Math.max(...segmentedNodeLineHeight); + return maxHeight; + } + removeGlue(nodeLine, frame) { + 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 = { + 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) { + var res = []; + 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); + } } - // generate the width and height of the stream - let defaultFontStyle = this.attrs["defaultFrameStyle"].textStyle; - calculateTextWidthHeight(preprocessed, defaultFontStyle); - // TODO - console.log(preprocessed); + return res; } } exports.Clo = Clo;