Skip to content

Commit

Permalink
Merge pull request #8 from archmoj/issue-br3d
Browse files Browse the repository at this point in the history
Implementation of break lines, sub & superscripts as well as bold & italic styles
  • Loading branch information
mikolalysenko authored Nov 17, 2018
2 parents 1c9b47b + a3f30a1 commit cc5cb50
Show file tree
Hide file tree
Showing 3 changed files with 1,879 additions and 15 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ Renders a string to a 2D cell complex
+ `options.orientation` determines the orientation of any output triangles/polygon curves. Must be either `"cw"` for clockwise or `"ccw"` for counter clockwise. Default is `"cw"`.
+ `options.canvas` an optional canvas element
+ `options.context` an optional canvas 2D context
+ `options.styletags.breaklines` if set, break-line tags i.e. < br > could be used in the input to enter new lines.
+ `options.styletags.bolds` if set, parts of the input i.e. between < b > and < /b > would be presented <b>bold</b>.
+ `options.styletags.italics` if set, parts of the input i.e. between < i > and < /i > would be presented <i>italic</i>.
+ `options.styletags.superscripts` if set, parts of the input i.e. between < sup > and < /sup > would be presented in as superscript. Multiple superscipts are also allowded. For example Line 0<sup>Line 1<sup>Line 2</sup></sup>.
+ `options.styletags.subscripts` if set, parts of the input i.e. between < sub > and < /sub > would be presented in as subscript. Multiple subscipts are also allowded. For example: Line 0<sub>Line 1<sub>Line 2</sub></sub>. Note: it is also possible to combine sub and superscripts: A<sub>B<sup>C</sup></sub>.

**Returns** The returned value depends on the type of geometry

Expand Down
278 changes: 263 additions & 15 deletions lib/vtext.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,72 @@ 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 = "</" + tag + ">"

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"
Expand Down Expand Up @@ -83,27 +149,169 @@ function transformPositions(positions, options, size) {
})
}

function getPixels(canvas, context, str, size) {
var width = Math.ceil(context.measureText(str).width + 2*size)|0
if(width > 8192) {
throw new Error("vectorize-text: String too long (sorry, this will get fixed later)")
function getPixels(canvas, context, rawString, fontSize, lineSpacing, styletags) {

rawString = rawString.replace(/\n/g, '') // don't accept \n in the input

if(styletags.breaklines === true) {
rawString = rawString.replace(/\<br\>/g, '\n') // replace <br> tags with \n in the string
} else {
rawString = rawString.replace(/\<br\>/g, ' ') // don't accept <br> 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
}
var height = 3 * size
if(canvas.height < height) {
canvas.height = height

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"
context.fillText(str, size, 2*size)
var i, j, xPos, yPos, zPos
var nDone = 0

//Cut pixels from image
var pixelData = context.getImageData(0, 0, width, height)
var pixels = ndarray(pixelData.data, [height, width, 4])
for(i = 0; i < numberOfLines; ++i) {

var txt = allTexts[i] + '\n'
xPos = 0
yPos = i * lineHeight
zPos = fontSize

var buffer = ""
function writeBuffer() {
if(buffer !== "") {
var delta = context.measureText(buffer).width

context.fillText(buffer, offsetX + xPos, offsetY + yPos)
xPos += delta
}
}

function changeStyle(oldStyle, newStyle) {

function getTextFontSize() {
return "" + Math.round(zPos) + "px ";
}

var ctxFont = "" + context.font;

if(styletags.subscripts === true) {
var oldIndex_Sub = oldStyle.indexOf(CHR_sub0);
var newIndex_Sub = newStyle.indexOf(CHR_sub0);

return pixels.pick(-1,-1,0).transpose(1,0)
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(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) {
Expand Down Expand Up @@ -190,8 +398,48 @@ function processPixels(pixels, options, size) {
}

function vectorizeText(str, canvas, context, options) {
var size = options.size || 64


var size = 64
var lineSpacing = 1.25
var styletags = {
breaklines: false,
bolds: false,
italics: false,
subscripts: false,
superscripts: false
}

if(options) {

if(options.size &&
options.size > 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,
Expand All @@ -203,7 +451,7 @@ function vectorizeText(str, canvas, context, options) {
context.textBaseline = "alphabetic"
context.direction = "ltr"

var pixels = getPixels(canvas, context, str, size)
var pixels = getPixels(canvas, context, str, size, lineSpacing, styletags)

return processPixels(pixels, options, size)
}
Loading

0 comments on commit cc5cb50

Please sign in to comment.