module.exports = vectorizeText module.exports.processPixels = processPixels var surfaceNets = require('surface-nets') var ndarray = require('ndarray') var simplify = require('simplify-planar-graph') var cleanPSLG = require('clean-pslg') var cdt2d = require('cdt2d') var toPolygonCrappy = require('planar-graph-to-polyline') var TAG_bold = "b" var CHR_bold = 'b|' var TAG_italic = "i" var CHR_italic = 'i|' var TAG_super = "sup" var CHR_super0 = '+' var CHR_super = '+1' var TAG_sub = "sub" var CHR_sub0 = '-' var CHR_sub = '-1' function parseTag(tag, TAG_CHR, str, map) { var opnTag = "<" + tag + ">" var clsTag = "" var nOPN = opnTag.length var nCLS = clsTag.length var isRecursive = (TAG_CHR[0] === CHR_super0) || (TAG_CHR[0] === CHR_sub0); var a = 0 var b = -nCLS while (a > -1) { a = str.indexOf(opnTag, a) if(a === -1) break b = str.indexOf(clsTag, a + nOPN) if(b === -1) break if(b <= a) break for(var i = a; i < b + nCLS; ++i){ if((i < a + nOPN) || (i >= b)) { map[i] = null str = str.substr(0, i) + " " + str.substr(i + 1) } else { if(map[i] !== null) { var pos = map[i].indexOf(TAG_CHR[0]) if(pos === -1) { map[i] += TAG_CHR } else { // i.e. to handle multiple sub/super-scripts if(isRecursive) { // i.e to increase the sub/sup number map[i] = map[i].substr(0, pos + 1) + (1 + parseInt(map[i][pos + 1])) + map[i].substr(pos + 2) } } } } } var start = a + nOPN var remainingStr = str.substr(start, b - start) var c = remainingStr.indexOf(opnTag) if(c !== -1) a = c else a = b + nCLS } return map } function transformPositions(positions, options, size) { var align = options.textAlign || "start" var baseline = options.textBaseline || "alphabetic" var lo = [1<<30, 1<<30] var hi = [0,0] var n = positions.length for(var i=0; i/g, '\n') // replace
tags with \n in the string } else { rawString = rawString.replace(/\/g, ' ') // don't accept
tags in the input and replace with space in this case } var activeStyle = "" var map = [] for(j = 0; j < rawString.length; ++j) { map[j] = activeStyle } if(styletags.bolds === true) map = parseTag(TAG_bold, CHR_bold, rawString, map) if(styletags.italics === true) map = parseTag(TAG_italic, CHR_italic, rawString, map) if(styletags.superscripts === true) map = parseTag(TAG_super, CHR_super, rawString, map) if(styletags.subscripts === true) map = parseTag(TAG_sub, CHR_sub, rawString, map) var allStyles = [] var plainText = "" for(j = 0; j < rawString.length; ++j) { if(map[j] !== null) { plainText += rawString[j] allStyles.push(map[j]) } } var allTexts = plainText.split('\n') var numberOfLines = allTexts.length var lineHeight = Math.round(lineSpacing * fontSize) var offsetX = fontSize var offsetY = fontSize * 2 var maxWidth = 0 var minHeight = numberOfLines * lineHeight + offsetY if(canvas.height < minHeight) { canvas.height = minHeight } context.fillStyle = "#000" context.fillRect(0, 0, canvas.width, canvas.height) context.fillStyle = "#fff" var i, j, xPos, yPos, zPos var nDone = 0 var buffer = "" function writeBuffer() { if(buffer !== "") { var delta = context.measureText(buffer).width context.fillText(buffer, offsetX + xPos, offsetY + yPos) xPos += delta } } function getTextFontSize() { return "" + Math.round(zPos) + "px "; } function changeStyle(oldStyle, newStyle) { var ctxFont = "" + context.font; if(styletags.subscripts === true) { var oldIndex_Sub = oldStyle.indexOf(CHR_sub0); var newIndex_Sub = newStyle.indexOf(CHR_sub0); var oldSub = (oldIndex_Sub > -1) ? parseInt(oldStyle[1 + oldIndex_Sub]) : 0; var newSub = (newIndex_Sub > -1) ? parseInt(newStyle[1 + newIndex_Sub]) : 0; if(oldSub !== newSub) { ctxFont = ctxFont.replace(getTextFontSize(), "?px ") zPos *= Math.pow(0.75, (newSub - oldSub)) ctxFont = ctxFont.replace("?px ", getTextFontSize()) } yPos += 0.25 * lineHeight * (newSub - oldSub); } if(styletags.superscripts === true) { var oldIndex_Super = oldStyle.indexOf(CHR_super0); var newIndex_Super = newStyle.indexOf(CHR_super0); var oldSuper = (oldIndex_Super > -1) ? parseInt(oldStyle[1 + oldIndex_Super]) : 0; var newSuper = (newIndex_Super > -1) ? parseInt(newStyle[1 + newIndex_Super]) : 0; if(oldSuper !== newSuper) { ctxFont = ctxFont.replace(getTextFontSize(), "?px ") zPos *= Math.pow(0.75, (newSuper - oldSuper)) ctxFont = ctxFont.replace("?px ", getTextFontSize()) } yPos -= 0.25 * lineHeight * (newSuper - oldSuper); } if(styletags.bolds === true) { var wasBold = (oldStyle.indexOf(CHR_bold) > -1) var is_Bold = (newStyle.indexOf(CHR_bold) > -1) if(!wasBold && is_Bold) { if(wasItalic) { ctxFont = ctxFont.replace("italic ", "italic bold ") } else { ctxFont = "bold " + ctxFont } } if(wasBold && !is_Bold) { ctxFont = ctxFont.replace("bold ", '') } } if(styletags.italics === true) { var wasItalic = (oldStyle.indexOf(CHR_italic) > -1) var is_Italic = (newStyle.indexOf(CHR_italic) > -1) if(!wasItalic && is_Italic) { ctxFont = "italic " + ctxFont } if(wasItalic && !is_Italic) { ctxFont = ctxFont.replace("italic ", '') } } context.font = ctxFont } for(i = 0; i < numberOfLines; ++i) { var txt = allTexts[i] + '\n' xPos = 0 yPos = i * lineHeight zPos = fontSize buffer = "" for(j = 0; j < txt.length; ++j) { var style = (j + nDone < allStyles.length) ? allStyles[j + nDone] : allStyles[allStyles.length - 1] if(activeStyle === style) { buffer += txt[j] } else { writeBuffer() buffer = txt[j] if(style !== undefined) { changeStyle(activeStyle, style) activeStyle = style } } } writeBuffer() nDone += txt.length var width = Math.round(xPos + 2 * offsetX) | 0 if(maxWidth < width) maxWidth = width } //Cut pixels from image var xCut = maxWidth var yCut = offsetY + lineHeight * numberOfLines var pixels = ndarray(context.getImageData(0, 0, xCut, yCut).data, [yCut, xCut, 4]) return pixels.pick(-1, -1, 0).transpose(1, 0) } function getContour(pixels, doSimplify) { var contour = surfaceNets(pixels, 128) if(doSimplify) { return simplify(contour.cells, contour.positions, 0.25) } return { edges: contour.cells, positions: contour.positions } } function processPixelsImpl(pixels, options, size, simplify) { //Extract contour var contour = getContour(pixels, simplify) //Apply warp to positions var positions = transformPositions(contour.positions, options, size) var edges = contour.edges var flip = "ccw" === options.orientation //Clean up the PSLG, resolve self intersections, etc. cleanPSLG(positions, edges) //If triangulate flag passed, triangulate the result if(options.polygons || options.polygon || options.polyline) { var result = toPolygonCrappy(edges, positions) var nresult = new Array(result.length) for(var i=0; i 0) size = options.size if(options.lineSpacing && options.lineSpacing > 0) lineSpacing = options.lineSpacing if(options.styletags && options.styletags.breaklines) styletags.breaklines = options.styletags.breaklines ? true : false if(options.styletags && options.styletags.bolds) styletags.bolds = options.styletags.bolds ? true : false if(options.styletags && options.styletags.italics) styletags.italics = options.styletags.italics ? true : false if(options.styletags && options.styletags.subscripts) styletags.subscripts = options.styletags.subscripts ? true : false if(options.styletags && options.styletags.superscripts) styletags.superscripts = options.styletags.superscripts ? true : false } context.font = [ options.fontStyle, options.fontVariant, options.fontWeight, size + "px", options.font ].filter(function(d) {return d}).join(" ") context.textAlign = "start" context.textBaseline = "alphabetic" context.direction = "ltr" var pixels = getPixels(canvas, context, str, size, lineSpacing, styletags) return processPixels(pixels, options, size) }