Show More
Commit Description:
use uuid cookie
Commit Description:
use uuid cookie
References:
File last commit:
Show/Diff file:
Action:
node_modules/pdfmake/src/textTools.js
| 364 lines
| 11.1 KiB
| application/javascript
| JavascriptLexer
|
r789 | 'use strict'; | |||
var isString = require('./helpers').isString; | ||||
var isNumber = require('./helpers').isNumber; | ||||
var isObject = require('./helpers').isObject; | ||||
var isArray = require('./helpers').isArray; | ||||
var isUndefined = require('./helpers').isUndefined; | ||||
var LineBreaker = require('linebreak'); | ||||
var LEADING = /^(\s)+/g; | ||||
var TRAILING = /(\s)+$/g; | ||||
/** | ||||
* Creates an instance of TextTools - text measurement utility | ||||
* | ||||
* @constructor | ||||
* @param {FontProvider} fontProvider | ||||
*/ | ||||
function TextTools(fontProvider) { | ||||
this.fontProvider = fontProvider; | ||||
} | ||||
/** | ||||
* Converts an array of strings (or inline-definition-objects) into a collection | ||||
* of inlines and calculated minWidth/maxWidth. | ||||
* and their min/max widths | ||||
* @param {Object} textArray - an array of inline-definition-objects (or strings) | ||||
* @param {Object} styleContextStack current style stack | ||||
* @return {Object} collection of inlines, minWidth, maxWidth | ||||
*/ | ||||
TextTools.prototype.buildInlines = function (textArray, styleContextStack) { | ||||
var measured = measure(this.fontProvider, textArray, styleContextStack); | ||||
var minWidth = 0, | ||||
maxWidth = 0, | ||||
currentLineWidth; | ||||
measured.forEach(function (inline) { | ||||
minWidth = Math.max(minWidth, inline.width - inline.leadingCut - inline.trailingCut); | ||||
if (!currentLineWidth) { | ||||
currentLineWidth = { width: 0, leadingCut: inline.leadingCut, trailingCut: 0 }; | ||||
} | ||||
currentLineWidth.width += inline.width; | ||||
currentLineWidth.trailingCut = inline.trailingCut; | ||||
maxWidth = Math.max(maxWidth, getTrimmedWidth(currentLineWidth)); | ||||
if (inline.lineEnd) { | ||||
currentLineWidth = null; | ||||
} | ||||
}); | ||||
if (getStyleProperty({}, styleContextStack, 'noWrap', false)) { | ||||
minWidth = maxWidth; | ||||
} | ||||
return { | ||||
items: measured, | ||||
minWidth: minWidth, | ||||
maxWidth: maxWidth | ||||
}; | ||||
function getTrimmedWidth(item) { | ||||
return Math.max(0, item.width - item.leadingCut - item.trailingCut); | ||||
} | ||||
}; | ||||
/** | ||||
* Returns size of the specified string (without breaking it) using the current style | ||||
* @param {String} text text to be measured | ||||
* @param {Object} styleContextStack current style stack | ||||
* @return {Object} size of the specified string | ||||
*/ | ||||
TextTools.prototype.sizeOfString = function (text, styleContextStack) { | ||||
text = text ? text.toString().replace(/\t/g, ' ') : ''; | ||||
//TODO: refactor - extract from measure | ||||
var fontName = getStyleProperty({}, styleContextStack, 'font', 'Roboto'); | ||||
var fontSize = getStyleProperty({}, styleContextStack, 'fontSize', 12); | ||||
var fontFeatures = getStyleProperty({}, styleContextStack, 'fontFeatures', null); | ||||
var bold = getStyleProperty({}, styleContextStack, 'bold', false); | ||||
var italics = getStyleProperty({}, styleContextStack, 'italics', false); | ||||
var lineHeight = getStyleProperty({}, styleContextStack, 'lineHeight', 1); | ||||
var characterSpacing = getStyleProperty({}, styleContextStack, 'characterSpacing', 0); | ||||
var font = this.fontProvider.provideFont(fontName, bold, italics); | ||||
return { | ||||
width: widthOfString(text, font, fontSize, characterSpacing, fontFeatures), | ||||
height: font.lineHeight(fontSize) * lineHeight, | ||||
fontSize: fontSize, | ||||
lineHeight: lineHeight, | ||||
ascender: font.ascender / 1000 * fontSize, | ||||
descender: font.descender / 1000 * fontSize | ||||
}; | ||||
}; | ||||
/** | ||||
* Returns size of the specified rotated string (without breaking it) using the current style | ||||
* | ||||
* @param {string} text text to be measured | ||||
* @param {number} angle | ||||
* @param {object} styleContextStack current style stack | ||||
* @returns {object} size of the specified string | ||||
*/ | ||||
TextTools.prototype.sizeOfRotatedText = function (text, angle, styleContextStack) { | ||||
var angleRad = angle * Math.PI / -180; | ||||
var size = this.sizeOfString(text, styleContextStack); | ||||
return { | ||||
width: Math.abs(size.height * Math.sin(angleRad)) + Math.abs(size.width * Math.cos(angleRad)), | ||||
height: Math.abs(size.width * Math.sin(angleRad)) + Math.abs(size.height * Math.cos(angleRad)) | ||||
}; | ||||
} | ||||
TextTools.prototype.widthOfString = function (text, font, fontSize, characterSpacing, fontFeatures) { | ||||
return widthOfString(text, font, fontSize, characterSpacing, fontFeatures); | ||||
}; | ||||
function splitWords(text, noWrap) { | ||||
var results = []; | ||||
text = text.replace(/\t/g, ' '); | ||||
if (noWrap) { | ||||
results.push({ text: text }); | ||||
return results; | ||||
} | ||||
var breaker = new LineBreaker(text); | ||||
var last = 0; | ||||
var bk; | ||||
while (bk = breaker.nextBreak()) { | ||||
var word = text.slice(last, bk.position); | ||||
if (bk.required || word.match(/\r?\n$|\r$/)) { // new line | ||||
word = word.replace(/\r?\n$|\r$/, ''); | ||||
results.push({ text: word, lineEnd: true }); | ||||
} else { | ||||
results.push({ text: word }); | ||||
} | ||||
last = bk.position; | ||||
} | ||||
return results; | ||||
} | ||||
function copyStyle(source, destination) { | ||||
destination = destination || {}; | ||||
source = source || {}; //TODO: default style | ||||
for (var key in source) { | ||||
if (key != 'text' && source.hasOwnProperty(key)) { | ||||
destination[key] = source[key]; | ||||
} | ||||
} | ||||
return destination; | ||||
} | ||||
function normalizeTextArray(array, styleContextStack) { | ||||
function flatten(array) { | ||||
return array.reduce(function (prev, cur) { | ||||
var current = isArray(cur.text) ? flatten(cur.text) : cur; | ||||
var more = [].concat(current).some(Array.isArray); | ||||
return prev.concat(more ? flatten(current) : current); | ||||
}, []); | ||||
} | ||||
function getOneWord(index, words, noWrap) { | ||||
if (isUndefined(words[index])) { | ||||
return null; | ||||
} | ||||
if (words[index].lineEnd) { | ||||
return null; | ||||
} | ||||
var word = words[index].text; | ||||
if (noWrap) { | ||||
var tmpWords = splitWords(normalizeString(word), false); | ||||
if (isUndefined(tmpWords[tmpWords.length - 1])) { | ||||
return null; | ||||
} | ||||
word = tmpWords[tmpWords.length - 1].text; | ||||
} | ||||
return word; | ||||
} | ||||
var results = []; | ||||
if (!isArray(array)) { | ||||
array = [array]; | ||||
} | ||||
array = flatten(array); | ||||
var lastWord = null; | ||||
for (var i = 0, l = array.length; i < l; i++) { | ||||
var item = array[i]; | ||||
var style = null; | ||||
var words; | ||||
var noWrap = getStyleProperty(item || {}, styleContextStack, 'noWrap', false); | ||||
if (isObject(item)) { | ||||
if (item._textRef && item._textRef._textNodeRef.text) { | ||||
item.text = item._textRef._textNodeRef.text; | ||||
} | ||||
words = splitWords(normalizeString(item.text), noWrap); | ||||
style = copyStyle(item); | ||||
} else { | ||||
words = splitWords(normalizeString(item), noWrap); | ||||
} | ||||
if (lastWord && words.length) { | ||||
var firstWord = getOneWord(0, words, noWrap); | ||||
var wrapWords = splitWords(normalizeString(lastWord + firstWord), false); | ||||
if (wrapWords.length === 1) { | ||||
results[results.length - 1].noNewLine = true; | ||||
} | ||||
} | ||||
for (var i2 = 0, l2 = words.length; i2 < l2; i2++) { | ||||
var result = { | ||||
text: words[i2].text | ||||
}; | ||||
if (words[i2].lineEnd) { | ||||
result.lineEnd = true; | ||||
} | ||||
copyStyle(style, result); | ||||
results.push(result); | ||||
} | ||||
lastWord = null; | ||||
if (i + 1 < l) { | ||||
lastWord = getOneWord(words.length - 1, words, noWrap); | ||||
} | ||||
} | ||||
return results; | ||||
} | ||||
function normalizeString(value) { | ||||
if (value === undefined || value === null) { | ||||
return ''; | ||||
} else if (isNumber(value)) { | ||||
return value.toString(); | ||||
} else if (isString(value)) { | ||||
return value; | ||||
} else { | ||||
return value.toString(); | ||||
} | ||||
} | ||||
function getStyleProperty(item, styleContextStack, property, defaultValue) { | ||||
var value; | ||||
if (item[property] !== undefined && item[property] !== null) { | ||||
// item defines this property | ||||
return item[property]; | ||||
} | ||||
if (!styleContextStack) { | ||||
return defaultValue; | ||||
} | ||||
styleContextStack.auto(item, function () { | ||||
value = styleContextStack.getProperty(property); | ||||
}); | ||||
if (value !== null && value !== undefined) { | ||||
return value; | ||||
} else { | ||||
return defaultValue; | ||||
} | ||||
} | ||||
function measure(fontProvider, textArray, styleContextStack) { | ||||
var normalized = normalizeTextArray(textArray, styleContextStack); | ||||
if (normalized.length) { | ||||
var leadingIndent = getStyleProperty(normalized[0], styleContextStack, 'leadingIndent', 0); | ||||
if (leadingIndent) { | ||||
normalized[0].leadingCut = -leadingIndent; | ||||
normalized[0].leadingIndent = leadingIndent; | ||||
} | ||||
} | ||||
normalized.forEach(function (item) { | ||||
var fontName = getStyleProperty(item, styleContextStack, 'font', 'Roboto'); | ||||
var fontSize = getStyleProperty(item, styleContextStack, 'fontSize', 12); | ||||
var fontFeatures = getStyleProperty(item, styleContextStack, 'fontFeatures', null); | ||||
var bold = getStyleProperty(item, styleContextStack, 'bold', false); | ||||
var italics = getStyleProperty(item, styleContextStack, 'italics', false); | ||||
var color = getStyleProperty(item, styleContextStack, 'color', 'black'); | ||||
var decoration = getStyleProperty(item, styleContextStack, 'decoration', null); | ||||
var decorationColor = getStyleProperty(item, styleContextStack, 'decorationColor', null); | ||||
var decorationStyle = getStyleProperty(item, styleContextStack, 'decorationStyle', null); | ||||
var background = getStyleProperty(item, styleContextStack, 'background', null); | ||||
var lineHeight = getStyleProperty(item, styleContextStack, 'lineHeight', 1); | ||||
var characterSpacing = getStyleProperty(item, styleContextStack, 'characterSpacing', 0); | ||||
var link = getStyleProperty(item, styleContextStack, 'link', null); | ||||
var linkToPage = getStyleProperty(item, styleContextStack, 'linkToPage', null); | ||||
var linkToDestination = getStyleProperty(item, styleContextStack, 'linkToDestination', null); | ||||
var noWrap = getStyleProperty(item, styleContextStack, 'noWrap', null); | ||||
var preserveLeadingSpaces = getStyleProperty(item, styleContextStack, 'preserveLeadingSpaces', false); | ||||
var preserveTrailingSpaces = getStyleProperty(item, styleContextStack, 'preserveTrailingSpaces', false); | ||||
var opacity = getStyleProperty(item, styleContextStack, 'opacity', 1); | ||||
var font = fontProvider.provideFont(fontName, bold, italics); | ||||
item.width = widthOfString(item.text, font, fontSize, characterSpacing, fontFeatures); | ||||
item.height = font.lineHeight(fontSize) * lineHeight; | ||||
if (!item.leadingCut) { | ||||
item.leadingCut = 0; | ||||
} | ||||
var leadingSpaces; | ||||
if (!preserveLeadingSpaces && (leadingSpaces = item.text.match(LEADING))) { | ||||
item.leadingCut += widthOfString(leadingSpaces[0], font, fontSize, characterSpacing, fontFeatures); | ||||
} | ||||
var trailingSpaces; | ||||
if (!preserveTrailingSpaces && (trailingSpaces = item.text.match(TRAILING))) { | ||||
item.trailingCut = widthOfString(trailingSpaces[0], font, fontSize, characterSpacing, fontFeatures); | ||||
} else { | ||||
item.trailingCut = 0; | ||||
} | ||||
item.alignment = getStyleProperty(item, styleContextStack, 'alignment', 'left'); | ||||
item.font = font; | ||||
item.fontSize = fontSize; | ||||
item.fontFeatures = fontFeatures; | ||||
item.characterSpacing = characterSpacing; | ||||
item.color = color; | ||||
item.decoration = decoration; | ||||
item.decorationColor = decorationColor; | ||||
item.decorationStyle = decorationStyle; | ||||
item.background = background; | ||||
item.link = link; | ||||
item.linkToPage = linkToPage; | ||||
item.linkToDestination = linkToDestination; | ||||
item.noWrap = noWrap; | ||||
item.opacity = opacity; | ||||
}); | ||||
return normalized; | ||||
} | ||||
function widthOfString(text, font, fontSize, characterSpacing, fontFeatures) { | ||||
return font.widthOfString(text, fontSize, fontFeatures) + ((characterSpacing || 0) * (text.length - 1)); | ||||
} | ||||
module.exports = TextTools; | ||||