UNPKG

@knoopx/react-pdf

Version:

<p align="center"> <img src="https://user-images.githubusercontent.com/5600341/27505816-c8bc37aa-587f-11e7-9a86-08a2d081a8b9.png" height="280px"> <p align="center">React renderer for creating PDF files on the browser and server<p> <p align="center">

1,599 lines (1,355 loc) 163 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var _inheritsLoose = _interopDefault(require('@babel/runtime/helpers/inheritsLoose')); var _objectWithoutPropertiesLoose = _interopDefault(require('@babel/runtime/helpers/objectWithoutPropertiesLoose')); var _extends = _interopDefault(require('@babel/runtime/helpers/extends')); var React = require('react'); var React__default = _interopDefault(React); var _regeneratorRuntime = _interopDefault(require('@babel/runtime/regenerator')); var _asyncToGenerator = _interopDefault(require('@babel/runtime/helpers/asyncToGenerator')); var BlobStream = _interopDefault(require('blob-stream')); var ReactFiberReconciler = _interopDefault(require('react-reconciler')); var _createClass = _interopDefault(require('@babel/runtime/helpers/createClass')); var PDFDocument = require('@react-pdf/pdfkit'); var PDFDocument__default = _interopDefault(PDFDocument); var Yoga = _interopDefault(require('yoga-layout-prebuilt')); var ramda = require('ramda'); var matchMedia = _interopDefault(require('media-engine')); var _assertThisInitialized = _interopDefault(require('@babel/runtime/helpers/assertThisInitialized')); var PDFRenderer$1 = _interopDefault(require('@react-pdf/textkit/renderers/pdf')); var AttributedString = _interopDefault(require('@react-pdf/textkit/attributedString')); require('is-url'); var fontkit = _interopDefault(require('@react-pdf/fontkit')); var fetch = _interopDefault(require('cross-fetch')); var layoutEngine = _interopDefault(require('@react-pdf/textkit/layout')); var linebreaker = _interopDefault(require('@react-pdf/textkit/engines/linebreaker')); var justification = _interopDefault(require('@react-pdf/textkit/engines/justification')); var textDecoration = _interopDefault(require('@react-pdf/textkit/engines/textDecoration')); var scriptItemizer = _interopDefault(require('@react-pdf/textkit/engines/scriptItemizer')); var wordHyphenation = _interopDefault(require('@react-pdf/textkit/engines/wordHyphenation')); var emojiRegex = _interopDefault(require('emoji-regex')); var PNG = _interopDefault(require('@react-pdf/png-js')); var _wrapPages = _interopDefault(require('page-wrapping')); function printWarning(format) { for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { args[_key - 1] = arguments[_key]; } var argIndex = 0; var message = 'Warning: ' + format.replace(/%s/g, function () { return args[argIndex++]; }); if (typeof console !== 'undefined') { console.error(message); } try { throw new Error(message); } catch (x) {} } var __DEV__ = process.env.NODE_ENV !== 'production'; var warning = __DEV__ ? function (condition, format) { if (format === undefined) { throw new Error('`warning(condition, format, ...args)` requires a warning ' + 'message argument'); } if (!condition) { for (var _len2 = arguments.length, args = new Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) { args[_key2 - 2] = arguments[_key2]; } printWarning.apply(void 0, [format].concat(args)); } } : function () {}; var Root = /*#__PURE__*/ function () { function Root() { this.isDirty = false; this.document = null; this.instance = null; } var _proto = Root.prototype; _proto.appendChild = function appendChild(child) { this.document = child; }; _proto.removeChild = function removeChild() { this.document.cleanup(); this.document = null; }; _proto.markDirty = function markDirty() { this.isDirty = true; }; _proto.cleanup = function cleanup() { this.document.cleanup(); }; _proto.finish = function finish() { this.document.finish(); }; _proto.render = /*#__PURE__*/ function () { var _render = _asyncToGenerator( /*#__PURE__*/ _regeneratorRuntime.mark(function _callee() { return _regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: this.instance = new PDFDocument__default({ autoFirstPage: false }); _context.next = 3; return this.document.render(); case 3: this.cleanup(); this.isDirty = false; case 5: case "end": return _context.stop(); } } }, _callee, this); })); function render() { return _render.apply(this, arguments); } return render; }(); _createClass(Root, [{ key: "name", get: function get() { return 'Root'; } }]); return Root; }(); var upperFirst = function upperFirst(value) { return value.charAt(0).toUpperCase() + value.slice(1); }; var isPercent = function isPercent(value) { return /((-)?\d+\.?\d*)%/g.exec(value); }; var matchPercent = function matchPercent(value) { var match = isPercent(value); if (match) { var _value = parseFloat(match[1], 10); var percent = _value / 100; return { value: _value, percent: percent, absValue: Math.abs(_value), absPercent: Math.abs(percent) }; } return null; }; var Node = /*#__PURE__*/ function () { function Node() { this.parent = null; this.children = []; this.computed = false; this.layout = Yoga.Node.createDefault(); } var _proto = Node.prototype; _proto.appendChild = function appendChild(child) { if (child) { child.parent = this; this.children.push(child); this.layout.insertChild(child.layout, this.layout.getChildCount()); } }; _proto.appendChildBefore = function appendChildBefore(child, beforeChild) { var index = this.children.indexOf(beforeChild); if (index !== -1 && child) { child.parent = this; this.children.splice(index, 0, child); this.layout.insertChild(child.layout, index); } }; _proto.removeChild = function removeChild(child) { var index = this.children.indexOf(child); if (index !== -1) { child.parent = null; this.children.splice(index, 1); this.layout.removeChild(child.layout); } child.cleanup(); }; _proto.removeAllChilds = function removeAllChilds() { var children = [].concat(this.children); for (var i = 0; i < children.length; i++) { children[i].remove(); } }; _proto.remove = function remove() { this.parent.removeChild(this); }; _proto.setDimension = function setDimension(attr, value) { var fixedMethod = "set" + upperFirst(attr); var percentMethod = fixedMethod + "Percent"; var percent = matchPercent(value); if (percent) { this.layout[percentMethod](percent.value); } else { this.layout[fixedMethod](value); } }; _proto.setPosition = function setPosition(edge, value) { var percent = matchPercent(value); if (percent) { this.layout.setPositionPercent(edge, percent.value); } else { this.layout.setPosition(edge, value); } }; _proto.setPadding = function setPadding(edge, value) { var percent = matchPercent(value); if (percent) { this.layout.setPaddingPercent(edge, percent.value); } else { this.layout.setPadding(edge, value); } }; _proto.setMargin = function setMargin(edge, value) { var percent = matchPercent(value); if (percent) { this.layout.setMarginPercent(edge, percent.value); } else { this.layout.setMargin(edge, value); } }; _proto.setBorder = function setBorder(edge, value) { if (matchPercent(value)) { throw new Error('Node: You cannot set percentage border widths'); } this.layout.setBorder(edge, value); }; _proto.getAbsoluteLayout = function getAbsoluteLayout() { var parent = this.parent; var parentLayout = parent && parent.getAbsoluteLayout ? parent.getAbsoluteLayout() : { left: 0, top: 0 }; return { left: this.left + parentLayout.left, top: this.top + parentLayout.top, height: this.height, width: this.width }; }; _proto.copyStyle = function copyStyle(node) { this.layout.copyStyle(node.layout); }; _proto.calculateLayout = function calculateLayout() { this.layout.calculateLayout(); this.computed = true; }; _proto.isEmpty = function isEmpty() { return this.children.length === 0; }; _proto.markDirty = function markDirty() { return this.layout.markDirty(); }; _proto.onAppendDynamically = function onAppendDynamically() {}; _proto.cleanup = function cleanup() { this.children.forEach(function (c) { return c.cleanup(); }); this.layout.unsetMeasureFunc(); Yoga.Node.destroy(this.layout); }; _createClass(Node, [{ key: "position", get: function get() { return this.layout.getPositionType() === Yoga.POSITION_TYPE_ABSOLUTE ? 'absolute' : 'relative'; }, set: function set(value) { this.layout.setPositionType(value === 'absolute' ? Yoga.POSITION_TYPE_ABSOLUTE : Yoga.POSITION_TYPE_RELATIVE); } }, { key: "top", get: function get() { return this.layout.getComputedTop() || 0; }, set: function set(value) { this.setPosition(Yoga.EDGE_TOP, value); } }, { key: "left", get: function get() { return this.layout.getComputedLeft() || 0; }, set: function set(value) { this.setPosition(Yoga.EDGE_LEFT, value); } }, { key: "right", get: function get() { return this.layout.getComputedRight() || 0; }, set: function set(value) { this.setPosition(Yoga.EDGE_RIGHT, value); } }, { key: "bottom", get: function get() { return this.layout.getComputedBottom() || 0; }, set: function set(value) { this.setPosition(Yoga.EDGE_BOTTOM, value); } }, { key: "width", get: function get() { return this.layout.getComputedWidth(); }, set: function set(value) { this.setDimension('width', value); } }, { key: "minWidth", get: function get() { return this.layout.getMinWidth().value; }, set: function set(value) { this.setDimension('minWidth', value); } }, { key: "maxWidth", get: function get() { return this.layout.getMaxWidth().value; }, set: function set(value) { this.setDimension('maxWidth', value); } }, { key: "height", get: function get() { return this.layout.getComputedHeight(); }, set: function set(value) { this.setDimension('height', value); } }, { key: "minHeight", get: function get() { return this.layout.getMinHeight().value; }, set: function set(value) { this.setDimension('minHeight', value); } }, { key: "maxHeight", get: function get() { return this.layout.getMaxHeight().value; }, set: function set(value) { this.setDimension('maxHeight', value); } }, { key: "paddingTop", get: function get() { return this.layout.getComputedPadding(Yoga.EDGE_TOP) || 0; }, set: function set(value) { this.setPadding(Yoga.EDGE_TOP, value); } }, { key: "paddingRight", get: function get() { return this.layout.getComputedPadding(Yoga.EDGE_RIGHT) || 0; }, set: function set(value) { this.setPadding(Yoga.EDGE_RIGHT, value); } }, { key: "paddingBottom", get: function get() { return this.layout.getComputedPadding(Yoga.EDGE_BOTTOM) || 0; }, set: function set(value) { this.setPadding(Yoga.EDGE_BOTTOM, value); } }, { key: "paddingLeft", get: function get() { return this.layout.getComputedPadding(Yoga.EDGE_LEFT) || 0; }, set: function set(value) { this.setPadding(Yoga.EDGE_LEFT, value); } }, { key: "marginTop", get: function get() { return this.layout.getComputedMargin(Yoga.EDGE_TOP) || 0; }, set: function set(value) { this.setMargin(Yoga.EDGE_TOP, value); } }, { key: "marginRight", get: function get() { return this.layout.getComputedMargin(Yoga.EDGE_RIGHT) || 0; }, set: function set(value) { this.setMargin(Yoga.EDGE_RIGHT, value); } }, { key: "marginBottom", get: function get() { return this.layout.getComputedMargin(Yoga.EDGE_BOTTOM) || 0; }, set: function set(value) { this.setMargin(Yoga.EDGE_BOTTOM, value); } }, { key: "marginLeft", get: function get() { return this.layout.getComputedMargin(Yoga.EDGE_LEFT) || 0; }, set: function set(value) { this.setMargin(Yoga.EDGE_LEFT, value); } }, { key: "borderTopWidth", get: function get() { return this.layout.getComputedBorder(Yoga.EDGE_TOP) || 0; }, set: function set(value) { this.setBorder(Yoga.EDGE_TOP, value); } }, { key: "borderRightWidth", get: function get() { return this.layout.getComputedBorder(Yoga.EDGE_RIGHT) || 0; }, set: function set(value) { this.setBorder(Yoga.EDGE_RIGHT, value); } }, { key: "borderBottomWidth", get: function get() { return this.layout.getComputedBorder(Yoga.EDGE_BOTTOM) || 0; }, set: function set(value) { this.setBorder(Yoga.EDGE_BOTTOM, value); } }, { key: "borderLeftWidth", get: function get() { return this.layout.getComputedBorder(Yoga.EDGE_LEFT) || 0; }, set: function set(value) { this.setBorder(Yoga.EDGE_LEFT, value); } }, { key: "padding", get: function get() { return { top: this.paddingTop, right: this.paddingRight, bottom: this.paddingBottom, left: this.paddingLeft }; }, set: function set(value) { this.paddingTop = value; this.paddingRight = value; this.paddingBottom = value; this.paddingLeft = value; } }, { key: "margin", get: function get() { return { top: this.marginTop, right: this.marginRight, bottom: this.marginBottom, left: this.marginLeft }; }, set: function set(value) { this.marginTop = value; this.marginRight = value; this.marginBottom = value; this.marginLeft = value; } }]); return Node; }(); var yogaValue = function yogaValue(prop, value) { var isAlignType = function isAlignType(prop) { return prop === 'alignItems' || prop === 'alignContent' || prop === 'alignSelf'; }; switch (value) { case 'auto': if (prop === 'alignSelf') { return Yoga.ALIGN_AUTO; } break; case 'flex': return Yoga.DISPLAY_FLEX; case 'none': return Yoga.DISPLAY_NONE; case 'row': return Yoga.FLEX_DIRECTION_ROW; case 'row-reverse': return Yoga.FLEX_DIRECTION_ROW_REVERSE; case 'column': return Yoga.FLEX_DIRECTION_COLUMN; case 'column-reverse': return Yoga.FLEX_DIRECTION_COLUMN_REVERSE; case 'stretch': return Yoga.ALIGN_STRETCH; case 'baseline': return Yoga.ALIGN_BASELINE; case 'space-around': if (prop === 'justifyContent') { return Yoga.JUSTIFY_SPACE_AROUND; } else if (isAlignType(prop)) { return Yoga.ALIGN_SPACE_AROUND; } break; case 'space-between': if (prop === 'justifyContent') { return Yoga.JUSTIFY_SPACE_BETWEEN; } else if (isAlignType(prop)) { return Yoga.ALIGN_SPACE_BETWEEN; } break; case 'around': return Yoga.JUSTIFY_SPACE_AROUND; case 'between': return Yoga.JUSTIFY_SPACE_BETWEEN; case 'wrap': return Yoga.WRAP_WRAP; case 'wrap-reverse': return Yoga.WRAP_WRAP_REVERSE; case 'nowrap': return Yoga.WRAP_NO_WRAP; case 'flex-start': if (prop === 'justifyContent') { return Yoga.JUSTIFY_FLEX_START; } else if (isAlignType(prop)) { return Yoga.ALIGN_FLEX_START; } break; case 'flex-end': if (prop === 'justifyContent') { return Yoga.JUSTIFY_FLEX_END; } else if (isAlignType(prop)) { return Yoga.ALIGN_FLEX_END; } break; case 'center': if (prop === 'justifyContent') { return Yoga.JUSTIFY_CENTER; } else if (isAlignType(prop)) { return Yoga.ALIGN_CENTER; } break; default: return value; } }; // These are not supported yet var DPI = 72; // 72pt per inch. var parseValue = function parseValue(value) { var match = /^(-?\d*\.?\d+)(in|mm|cm|pt|vh|vw)?$/g.exec(value); if (match) { return { value: parseFloat(match[1], 10), unit: match[2] || 'pt' }; } return { value: value, unit: undefined }; }; var parseScalar = function parseScalar(value, container) { var scalar = parseValue(value); switch (scalar.unit) { case 'in': return scalar.value * DPI; case 'mm': return scalar.value * (1 / 25.4) * DPI; case 'cm': return scalar.value * (1 / 2.54) * DPI; case 'vh': if (container.isAutoHeight) { throw new Error('vh unit not supported in auto-height pages. Please specify page height if you want to use vh.'); } return scalar.value * (container.height / 100); case 'vw': return scalar.value * (container.width / 100); default: return scalar.value; } }; var isBorderStyle = function isBorderStyle(key, value) { return key.match(/^border/) && typeof value === 'string'; }; var matchBorderShorthand = function matchBorderShorthand(value) { return value.match(/(\d+(px|in|mm|cm|pt|vw|vh)?)\s(\S+)\s(\S+)/); }; // Transforms shorthand border values var processBorders = function processBorders(key, value) { var match = matchBorderShorthand(value); if (match) { if (key.match(/.Color/)) { return match[4]; } else if (key.match(/.Style/)) { return match[3]; } else if (key.match(/.Width/)) { return match[1]; } else { throw new Error("StyleSheet: Invalid '" + value + "' for '" + key + "'"); } } return value; }; var isBoxModelStyle = function isBoxModelStyle(key, value) { return key.match(/^(margin)|(padding)/) && typeof value === 'string'; }; var matchBoxModel = function matchBoxModel(value) { return value.match(/\d+(px|in|mm|cm|pt|%|vw|vh)?/g); }; // Transforms shorthand margin and padding values var processBoxModel = function processBoxModel(key, value) { var match = matchBoxModel(value); if (match) { if (key.match(/.Top/)) { return match[0]; } else if (key.match(/.Right/)) { return match[1] || match[0]; } else if (key.match(/.Bottom/)) { return match[2] || match[0]; } else if (key.match(/.Left/)) { return match[3] || match[1] || match[0]; } else { throw new Error("StyleSheet: Invalid '" + value + "' for '" + key + "'"); } } return value; }; // https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight#Common_weight_name_mapping var FONT_WEIGHTS = { thin: 100, hairline: 100, ultralight: 200, extralight: 200, light: 300, normal: 400, medium: 500, semibold: 600, demibold: 600, bold: 700, ultrabold: 800, extrabold: 800, heavy: 900, black: 900 }; var isFontWeightStyle = function isFontWeightStyle(key) { return key.match(/^fontWeight/); }; var processFontWeight = function processFontWeight(value) { if (!value) return FONT_WEIGHTS.normal; if (typeof value === 'number') return value; return FONT_WEIGHTS[value.toLowerCase()]; }; var isObjectPositionStyle = function isObjectPositionStyle(key, value) { return key.match(/^objectPosition/) && typeof value === 'string'; }; var matchObjectPosition = function matchObjectPosition(value) { return value.match(/\d+(px|in|mm|cm|pt|%|vw|vh)?/g); }; // Transforms shorthand objectPosition values var processObjectPosition = function processObjectPosition(key, value) { var match = matchObjectPosition(value); if (match) { if (key.match(/.X/)) { return match[0]; } else if (key.match(/.Y/)) { return match[1]; } else { throw new Error("StyleSheet: Invalid '" + value + "' for '" + key + "'"); } } return value; }; var isTransformOriginStyle = function isTransformOriginStyle(key, value) { return key.match(/^transformOrigin/) && typeof value === 'string'; }; var matchTransformOrigin = function matchTransformOrigin(value) { return value.match(/(-?\d+(px|in|mm|cm|pt|%|vw|vh)?)|top|right|bottom|left|center/g); }; var transformOffsetKeywords = function transformOffsetKeywords(value) { switch (value) { case 'top': case 'left': return '0%'; case 'right': case 'bottom': return '100%'; case 'center': return '50%'; default: return value; } }; // Transforms shorthand transformOrigin values var processTransformOrigin = function processTransformOrigin(key, value) { var match = matchTransformOrigin(value); if (match) { var result; if (key.match(/.X/)) { result = match[0]; } else if (key.match(/.Y/)) { result = match[1] || match[0]; } else { throw new Error("StyleSheet: Invalid '" + value + "' for '" + key + "'"); } return transformOffsetKeywords(result); } return value; }; var hasOwnProperty = Object.prototype.hasOwnProperty; var styleShorthands = { margin: { marginTop: true, marginRight: true, marginBottom: true, marginLeft: true }, marginHorizontal: { marginLeft: true, marginRight: true }, marginVertical: { marginTop: true, marginBottom: true }, padding: { paddingTop: true, paddingRight: true, paddingBottom: true, paddingLeft: true }, paddingHorizontal: { paddingLeft: true, paddingRight: true }, paddingVertical: { paddingTop: true, paddingBottom: true }, border: { borderTopColor: true, borderTopStyle: true, borderTopWidth: true, borderRightColor: true, borderRightStyle: true, borderRightWidth: true, borderBottomColor: true, borderBottomStyle: true, borderBottomWidth: true, borderLeftColor: true, borderLeftStyle: true, borderLeftWidth: true }, borderTop: { borderTopColor: true, borderTopStyle: true, borderTopWidth: true }, borderRight: { borderRightColor: true, borderRightStyle: true, borderRightWidth: true }, borderBottom: { borderBottomColor: true, borderBottomStyle: true, borderBottomWidth: true }, borderLeft: { borderLeftColor: true, borderLeftStyle: true, borderLeftWidth: true }, borderColor: { borderTopColor: true, borderRightColor: true, borderBottomColor: true, borderLeftColor: true }, borderRadius: { borderTopLeftRadius: true, borderTopRightRadius: true, borderBottomRightRadius: true, borderBottomLeftRadius: true }, borderStyle: { borderTopStyle: true, borderRightStyle: true, borderBottomStyle: true, borderLeftStyle: true }, borderWidth: { borderTopWidth: true, borderRightWidth: true, borderBottomWidth: true, borderLeftWidth: true }, objectPosition: { objectPositionX: true, objectPositionY: true }, transformOrigin: { transformOriginX: true, transformOriginY: true } }; // Expand the shorthand properties to isolate every declaration from the others. var expandStyles = function expandStyles(style) { if (!style) return style; var propsArray = Object.keys(style); var resolvedStyle = {}; for (var i = 0; i < propsArray.length; i++) { var key = propsArray[i]; var value = style[key]; switch (key) { case 'display': case 'flex': case 'flexDirection': case 'flexWrap': case 'flexFlow': case 'flexGrow': case 'flexShrink': case 'flexBasis': case 'justifyContent': case 'alignSelf': case 'alignItems': case 'alignContent': case 'order': resolvedStyle[key] = yogaValue(key, value); break; case 'textAlignVertical': resolvedStyle.verticalAlign = value === 'center' ? 'middle' : value; break; case 'margin': case 'marginHorizontal': case 'marginVertical': case 'padding': case 'paddingHorizontal': case 'paddingVertical': case 'border': case 'borderTop': case 'borderRight': case 'borderBottom': case 'borderLeft': case 'borderColor': case 'borderRadius': case 'borderStyle': case 'borderWidth': case 'objectPosition': case 'transformOrigin': { var expandedProps = styleShorthands[key]; for (var propName in expandedProps) { if (hasOwnProperty.call(expandedProps, propName)) { resolvedStyle[propName] = value; } } } break; default: resolvedStyle[key] = value; break; } } return resolvedStyle; }; var transformStyles = function transformStyles(style, container) { var expandedStyles = expandStyles(style); var propsArray = Object.keys(expandedStyles); var resolvedStyle = {}; for (var i = 0; i < propsArray.length; i++) { var key = propsArray[i]; var value = expandedStyles[key]; var resolved = void 0; if (isBorderStyle(key, value)) { resolved = processBorders(key, value); } else if (isBoxModelStyle(key, value)) { resolved = processBoxModel(key, value); } else if (isObjectPositionStyle(key, value)) { resolved = processObjectPosition(key, value); } else if (isTransformOriginStyle(key, value)) { resolved = processTransformOrigin(key, value); } else if (isFontWeightStyle(key)) { resolved = processFontWeight(value); } else { resolved = value; } resolvedStyle[key] = parseScalar(resolved, container); } return resolvedStyle; }; var create = function create(styles) { return styles; }; var flatten = function flatten(input) { if (!Array.isArray(input)) { input = [input]; } var result = input.reduce(function (acc, style) { if (style) { var s = Array.isArray(style) ? flatten(style) : style; Object.keys(s).forEach(function (key) { if (s[key] !== null && s[key] !== undefined) { acc[key] = s[key]; } }); } return acc; }, {}); return result; }; var resolveMediaQueries = function resolveMediaQueries(input, container) { var result = Object.keys(input).reduce(function (acc, key) { var _extends2; if (/@media/.test(key)) { var _matchMedia; return _extends({}, acc, {}, matchMedia((_matchMedia = {}, _matchMedia[key] = input[key], _matchMedia), container)); } return _extends({}, acc, (_extends2 = {}, _extends2[key] = input[key], _extends2)); }, {}); return result; }; var resolve = function resolve(styles, container) { if (!styles) return null; styles = flatten(styles); styles = resolveMediaQueries(styles, container); styles = transformStyles(styles, container); return styles; }; var absoluteFillObject = { position: 'absolute', top: 0, left: 0, bottom: 0, right: 0 }; var StyleSheet = { hairlineWidth: 1, create: create, resolve: resolve, flatten: flatten, absoluteFillObject: absoluteFillObject }; var Debug = { debug: function debug() { var layout = this.getAbsoluteLayout(); var padding = this.padding; var margin = this.margin; this.root.instance.save(); this.debugContent(layout, margin, padding); this.debugPadding(layout, margin, padding); this.debugMargin(layout, margin); this.debugText(layout, margin); this.debugOrigin(); this.root.instance.restore(); }, debugOrigin: function debugOrigin() { if (this.style.transform) { var origin = this.origin; this.root.instance.circle(origin[0], origin[1], 3).fill('red').circle(origin[0], origin[1], 5).stroke('red'); } }, debugText: function debugText(layout, margin) { var roundedWidth = Math.round(this.width + margin.left + margin.right); var roundedHeight = Math.round(this.height + margin.top + margin.bottom); this.root.instance.fontSize(4).opacity(1).fillColor('black').text(roundedWidth + " x " + roundedHeight, layout.left - margin.left, Math.max(layout.top - margin.top - 4, 1)); }, debugContent: function debugContent(layout, margin, padding) { this.root.instance.fillColor('#a1c6e7').opacity(0.5).rect(layout.left + padding.left, layout.top + padding.top, layout.width - padding.left - padding.right, layout.height - padding.top - padding.bottom).fill(); }, debugPadding: function debugPadding(layout, margin, padding) { this.root.instance.fillColor('#c4deb9').opacity(0.5); // Padding top this.root.instance.rect(layout.left + padding.left, layout.top, layout.width - padding.right - padding.left, padding.top).fill(); // Padding left this.root.instance.rect(layout.left, layout.top, padding.left, layout.height).fill(); // Padding right this.root.instance.rect(layout.left + layout.width - padding.right, layout.top, padding.right, layout.height).fill(); // Padding bottom this.root.instance.rect(layout.left + padding.left, layout.top + layout.height - padding.bottom, layout.width - padding.right - padding.left, padding.bottom).fill(); }, debugMargin: function debugMargin(layout, margin) { this.root.instance.fillColor('#f8cca1').opacity(0.5); // Margin top this.root.instance.rect(layout.left, layout.top - margin.top, layout.width, margin.top).fill(); // Margin left this.root.instance.rect(layout.left - margin.left, layout.top - margin.top, margin.left, layout.height + margin.top + margin.bottom).fill(); // Margin right this.root.instance.rect(layout.left + layout.width, layout.top - margin.top, margin.right, layout.height + margin.top + margin.bottom).fill(); // Margin bottom this.root.instance.rect(layout.left, layout.top + layout.height, layout.width, margin.bottom).fill(); } }; // Ref: https://www.w3.org/TR/css-backgrounds-3/#borders // This constant is used to approximate a symmetrical arc using a cubic Bezier curve. var KAPPA = 4.0 * ((Math.sqrt(2) - 1.0) / 3.0); function drawBorders() { var instance = this.root.instance; var layout = this.getAbsoluteLayout(); var borderTopWidth = this.borderTopWidth, borderLeftWidth = this.borderLeftWidth, borderRightWidth = this.borderRightWidth, borderBottomWidth = this.borderBottomWidth; var _this$style = this.style, opacity = _this$style.opacity, _this$style$borderTop = _this$style.borderTopLeftRadius, borderTopLeftRadius = _this$style$borderTop === void 0 ? 0 : _this$style$borderTop, _this$style$borderTop2 = _this$style.borderTopRightRadius, borderTopRightRadius = _this$style$borderTop2 === void 0 ? 0 : _this$style$borderTop2, _this$style$borderBot = _this$style.borderBottomLeftRadius, borderBottomLeftRadius = _this$style$borderBot === void 0 ? 0 : _this$style$borderBot, _this$style$borderBot2 = _this$style.borderBottomRightRadius, borderBottomRightRadius = _this$style$borderBot2 === void 0 ? 0 : _this$style$borderBot2, _this$style$borderTop3 = _this$style.borderTopColor, borderTopColor = _this$style$borderTop3 === void 0 ? 'black' : _this$style$borderTop3, _this$style$borderTop4 = _this$style.borderTopStyle, borderTopStyle = _this$style$borderTop4 === void 0 ? 'solid' : _this$style$borderTop4, _this$style$borderLef = _this$style.borderLeftColor, borderLeftColor = _this$style$borderLef === void 0 ? 'black' : _this$style$borderLef, _this$style$borderLef2 = _this$style.borderLeftStyle, borderLeftStyle = _this$style$borderLef2 === void 0 ? 'solid' : _this$style$borderLef2, _this$style$borderRig = _this$style.borderRightColor, borderRightColor = _this$style$borderRig === void 0 ? 'black' : _this$style$borderRig, _this$style$borderRig2 = _this$style.borderRightStyle, borderRightStyle = _this$style$borderRig2 === void 0 ? 'solid' : _this$style$borderRig2, _this$style$borderBot3 = _this$style.borderBottomColor, borderBottomColor = _this$style$borderBot3 === void 0 ? 'black' : _this$style$borderBot3, _this$style$borderBot4 = _this$style.borderBottomStyle, borderBottomStyle = _this$style$borderBot4 === void 0 ? 'solid' : _this$style$borderBot4; var style = { borderTopColor: borderTopColor, borderTopWidth: borderTopWidth, borderTopStyle: borderTopStyle, borderLeftColor: borderLeftColor, borderLeftWidth: borderLeftWidth, borderLeftStyle: borderLeftStyle, borderRightColor: borderRightColor, borderRightWidth: borderRightWidth, borderRightStyle: borderRightStyle, borderBottomColor: borderBottomColor, borderBottomWidth: borderBottomWidth, borderBottomStyle: borderBottomStyle, borderTopLeftRadius: borderTopLeftRadius, borderTopRightRadius: borderTopRightRadius, borderBottomLeftRadius: borderBottomLeftRadius, borderBottomRightRadius: borderBottomRightRadius }; var width = layout.width, height = layout.height; var rtr = Math.min(borderTopRightRadius, 0.5 * width, 0.5 * height); var rtl = Math.min(borderTopLeftRadius, 0.5 * width, 0.5 * height); var rbr = Math.min(borderBottomRightRadius, 0.5 * width, 0.5 * height); var rbl = Math.min(borderBottomLeftRadius, 0.5 * width, 0.5 * height); instance.save(); instance.strokeOpacity(opacity); if (borderTopWidth) { instance.save(); clipBorderTop(instance, layout, style, rtr, rtl); fillBorderTop(instance, layout, style, rtr, rtl); instance.restore(); } if (borderRightWidth) { instance.save(); clipBorderRight(instance, layout, style, rtr, rbr); fillBorderRight(instance, layout, style, rtr, rbr); instance.restore(); } if (borderBottomWidth) { instance.save(); clipBorderBottom(instance, layout, style, rbl, rbr); fillBorderBottom(instance, layout, style, rbl, rbr); instance.restore(); } if (borderLeftWidth) { instance.save(); clipBorderLeft(instance, layout, style, rbl, rtl); fillBorderLeft(instance, layout, style, rbl, rtl); instance.restore(); } instance.restore(); } var clipBorderTop = function clipBorderTop(ctx, layout, style, rtr, rtl) { var top = layout.top, left = layout.left, width = layout.width, height = layout.height; var borderTopWidth = style.borderTopWidth, borderRightWidth = style.borderRightWidth, borderLeftWidth = style.borderLeftWidth; // Clip outer top border edge ctx.moveTo(left + rtl, top); ctx.lineTo(left + width - rtr, top); // Ellipse coefficients outer top right cap var c0 = rtr * (1.0 - KAPPA); // Clip outer top right cap ctx.bezierCurveTo(left + width - c0, top, left + width, top + c0, left + width, top + rtr); // Move down in case the margin exceedes the radius var topRightYCoord = top + Math.max(borderTopWidth, rtr); ctx.lineTo(left + width, topRightYCoord); // Clip inner top right cap ctx.lineTo(left + width - borderRightWidth, topRightYCoord); // Ellipse coefficients inner top right cap var innerTopRightRadiusX = Math.max(rtr - borderRightWidth, 0); var innerTopRightRadiusY = Math.max(rtr - borderTopWidth, 0); var c1 = innerTopRightRadiusX * (1.0 - KAPPA); var c2 = innerTopRightRadiusY * (1.0 - KAPPA); // Clip inner top right cap ctx.bezierCurveTo(left + width - borderRightWidth, top + borderTopWidth + c2, left + width - borderRightWidth - c1, top + borderTopWidth, left + width - borderRightWidth - innerTopRightRadiusX, top + borderTopWidth); // Clip inner top border edge ctx.lineTo(left + Math.max(rtl, borderLeftWidth), top + borderTopWidth); // Ellipse coefficients inner top left cap var innerTopLeftRadiusX = Math.max(rtl - borderLeftWidth, 0); var innerTopLeftRadiusY = Math.max(rtl - borderTopWidth, 0); var c3 = innerTopLeftRadiusX * (1.0 - KAPPA); var c4 = innerTopLeftRadiusY * (1.0 - KAPPA); var topLeftYCoord = top + Math.max(borderTopWidth, rtl); // Clip inner top left cap ctx.bezierCurveTo(left + borderLeftWidth + c3, top + borderTopWidth, left + borderLeftWidth, top + borderTopWidth + c4, left + borderLeftWidth, topLeftYCoord); ctx.lineTo(left, topLeftYCoord); // Move down in case the margin exceedes the radius ctx.lineTo(left, top + rtl); // Ellipse coefficients outer top left cap var c5 = rtl * (1.0 - KAPPA); // Clip outer top left cap ctx.bezierCurveTo(left, top + c5, left + c5, top, left + rtl, top); ctx.closePath(); ctx.clip(); // Clip border top cap joins if (borderRightWidth) { var trSlope = -borderTopWidth / borderRightWidth; ctx.moveTo(left + width / 2, trSlope * (-width / 2) + top); ctx.lineTo(left + width, top); ctx.lineTo(left, top); ctx.lineTo(left, top + height); ctx.closePath(); ctx.clip(); } if (borderLeftWidth) { var _trSlope = -borderTopWidth / borderLeftWidth; ctx.moveTo(left + width / 2, _trSlope * (-width / 2) + top); ctx.lineTo(left, top); ctx.lineTo(left + width, top); ctx.lineTo(left + width, top + height); ctx.closePath(); ctx.clip(); } }; var fillBorderTop = function fillBorderTop(ctx, layout, style, rtr, rtl) { var top = layout.top, left = layout.left, width = layout.width; var borderTopColor = style.borderTopColor, borderTopWidth = style.borderTopWidth, borderTopStyle = style.borderTopStyle, borderRightWidth = style.borderRightWidth, borderLeftWidth = style.borderLeftWidth; var c0 = rtl * (1.0 - KAPPA); var c1 = rtr * (1.0 - KAPPA); ctx.moveTo(left, top + Math.max(rtl, borderTopWidth)); ctx.bezierCurveTo(left, top + c0, left + c0, top, left + rtl, top); ctx.lineTo(left + width - rtr, top); ctx.bezierCurveTo(left + width - c1, top, left + width, top + c1, left + width, top + rtr); ctx.strokeColor(borderTopColor); ctx.lineWidth(Math.max(borderRightWidth, borderTopWidth, borderLeftWidth) * 2); if (borderTopStyle === 'dashed') { ctx.dash(borderTopWidth * 2, { space: borderTopWidth * 1.2 }); } else if (borderTopStyle === 'dotted') { ctx.dash(borderTopWidth, { space: borderTopWidth * 1.2 }); } ctx.stroke(); ctx.undash(); }; var clipBorderRight = function clipBorderRight(ctx, layout, style, rtr, rbr) { var top = layout.top, left = layout.left, width = layout.width, height = layout.height; var borderTopWidth = style.borderTopWidth, borderRightWidth = style.borderRightWidth, borderBottomWidth = style.borderBottomWidth; // Clip outer right border edge ctx.moveTo(left + width, top + rtr); ctx.lineTo(left + width, top + height - rbr); // Ellipse coefficients outer bottom right cap var c0 = rbr * (1.0 - KAPPA); // Clip outer top right cap ctx.bezierCurveTo(left + width, top + height - c0, left + width - c0, top + height, left + width - rbr, top + height); // Move left in case the margin exceedes the radius var topBottomXCoord = left + width - Math.max(borderRightWidth, rbr); ctx.lineTo(topBottomXCoord, top + height); // Clip inner bottom right cap ctx.lineTo(topBottomXCoord, top + height - borderBottomWidth); // Ellipse coefficients inner bottom right cap var innerBottomRightRadiusX = Math.max(rbr - borderRightWidth, 0); var innerBottomRightRadiusY = Math.max(rbr - borderBottomWidth, 0); var c1 = innerBottomRightRadiusX * (1.0 - KAPPA); var c2 = innerBottomRightRadiusY * (1.0 - KAPPA); // Clip inner top right cap ctx.bezierCurveTo(left + width - borderRightWidth - c1, top + height - borderBottomWidth, left + width - borderRightWidth, top + height - borderBottomWidth - c2, left + width - borderRightWidth, top + height - Math.max(rbr, borderBottomWidth)); // Clip inner right border edge ctx.lineTo(left + width - borderRightWidth, top + Math.max(rtr, borderTopWidth)); // Ellipse coefficients inner top right cap var innerTopRightRadiusX = Math.max(rtr - borderRightWidth, 0); var innerTopRightRadiusY = Math.max(rtr - borderTopWidth, 0); var c3 = innerTopRightRadiusX * (1.0 - KAPPA); var c4 = innerTopRightRadiusY * (1.0 - KAPPA); var topRightXCoord = left + width - Math.max(rtr, borderRightWidth); // Clip inner top left cap ctx.bezierCurveTo(left + width - borderRightWidth, top + borderTopWidth + c4, left + width - borderRightWidth - c3, top + borderTopWidth, topRightXCoord, top + borderTopWidth); ctx.lineTo(topRightXCoord, top); // Move right in case the margin exceedes the radius ctx.lineTo(left + width - rtr, top); // Ellipse coefficients outer top right cap var c5 = rtr * (1.0 - KAPPA); // Clip outer top right cap ctx.bezierCurveTo(left + width - c5, top, left + width, top + c5, left + width, top + rtr); ctx.closePath(); ctx.clip(); // Clip border right cap joins if (borderTopWidth) { var trSlope = -borderTopWidth / borderRightWidth; ctx.moveTo(left + width / 2, trSlope * (-width / 2) + top); ctx.lineTo(left + width, top); ctx.lineTo(left + width, top + height); ctx.lineTo(left, top + height); ctx.closePath(); ctx.clip(); } if (borderBottomWidth) { var brSlope = borderBottomWidth / borderRightWidth; ctx.moveTo(left + width / 2, brSlope * (-width / 2) + top + height); ctx.lineTo(left + width, top + height); ctx.lineTo(left + width, top); ctx.lineTo(left, top); ctx.closePath(); ctx.clip(); } }; var fillBorderRight = function fillBorderRight(ctx, layout, style, rtr, rbr) { var top = layout.top, left = layout.left, width = layout.width, height = layout.height; var borderRightColor = style.borderRightColor, borderRightStyle = style.borderRightStyle, borderRightWidth = style.borderRightWidth, borderTopWidth = style.borderTopWidth, borderBottomWidth = style.borderBottomWidth; var c0 = rbr * (1.0 - KAPPA); var c1 = rtr * (1.0 - KAPPA); ctx.moveTo(left + width - rtr, top); ctx.bezierCurveTo(left + width - c1, top, left + width, top + c1, left + width, top + rtr); ctx.lineTo(left + width, top + height - rbr); ctx.bezierCurveTo(left + width, top + height - c0, left + width - c0, top + height, left + width - rbr, top + height); ctx.strokeColor(borderRightColor); ctx.lineWidth(Math.max(borderRightWidth, borderTopWidth, borderBottomWidth) * 2); if (borderRightStyle === 'dashed') { ctx.dash(borderRightWidth * 2, { space: borderRightWidth * 1.2 }); } else if (borderRightStyle === 'dotted') { ctx.dash(borderRightWidth, { space: borderRightWidth * 1.2 }); } ctx.stroke(); ctx.undash(); }; var clipBorderBottom = function clipBorderBottom(ctx, layout, style, rbl, rbr) { var top = layout.top, left = layout.left, width = layout.width, height = layout.height; var borderBottomWidth = style.borderBottomWidth, borderRightWidth = style.borderRightWidth, borderLeftWidth = style.borderLeftWidth; // Clip outer top border edge ctx.moveTo(left + width - rbr, top + height); ctx.lineTo(left + rbl, top + height); // Ellipse coefficients outer top right cap var c0 = rbl * (1.0 - KAPPA); // Clip outer top right cap ctx.bezierCurveTo(left + c0, top + height, left, top + height - c0, left, top + height - rbl); // Move up in case the margin exceedes the radius var bottomLeftYCoord = top + height - Math.max(borderBottomWidth, rbl); ctx.lineTo(left, bottomLeftYCoord); // Clip inner bottom left cap ctx.lineTo(left + borderLeftWidth, bottomLeftYCoord); // Ellipse coefficients inner top right cap var innerBottomLeftRadiusX = Math.max(rbl - borderLeftWidth, 0); var innerBottomLeftRadiusY = Math.max(rbl - borderBottomWidth, 0); var c1 = innerBottomLeftRadiusX * (1.0 - KAPPA); var c2 = innerBottomLeftRadiusY * (1.0 - KAPPA); // Clip inner bottom left cap ctx.bezierCurveTo(left + borderLeftWidth, top + height - borderBottomWidth - c2, left + borderLeftWidth + c1, top + height - borderBottomWidth, left + borderLeftWidth + innerBottomLeftRadiusX, top + height - borderBottomWidth); // Clip inner bottom border edge ctx.lineTo(left + width - Math.max(rbr, borderRightWidth), top + height - borderBottomWidth); // Ellipse coefficients inner top left cap var innerBottomRightRadiusX = Math.max(rbr - borderRightWidth, 0); var innerBottomRightRadiusY = Math.max(rbr - borderBottomWidth, 0); var c3 = innerBottomRightRadiusX * (1.0 - KAPPA); var c4 = innerBottomRightRadiusY * (1.0 - KAPPA); var bottomRightYCoord = top + height - Math.max(borderBottomWidth, rbr); // Clip inner top left cap ctx.bezierCurveTo(left + width - borderRightWidth - c3, top + height - borderBottomWidth, left + width - borderRightWidth, top + height - borderBottomWidth - c4, left + width - borderRightWidth, bottomRightYCoord); ctx.lineTo(left + width, bottomRightYCoord); // Move down in case the margin exceedes the radius ctx.lineTo(left + width, top + height - rbr); // Ellipse coefficients outer top left cap var c5 = rbr * (1.0 - KAPPA); // Clip outer top left cap ctx.bezierCurveTo(left + width, top + height - c5, left + width - c5, top + height, left + width - rbr, top + height); ctx.closePath(); ctx.clip(); // Clip border bottom cap joins if (borderRightWidth) { var brSlope = borderBottomWidth / borderRightWidth; ctx.moveTo(left + width / 2, brSlope * (-width / 2) + top + height); ctx.lineTo(left + width, top + height); ctx.lineTo(left, top + height); ctx.lineTo(left, top); ctx.closePath(); ctx.clip(); } if (borderLeftWidth) { var trSlope = -borderBottomWidth / borderLeftWidth; ctx.moveTo(left + width / 2, trSlope * (width / 2) + top + height); ctx.lineTo(left, top + height); ctx.lineTo(left + width, top + height); ctx.lineTo(left + width, top); ctx.closePath(); ctx.clip(); } }; var fillBorderBottom = function fillBorderBottom(ctx, layout, style, rbl, rbr) { var top = layout.top, left = layout.left, width = layout.width, height = layout.height; var borderBottomColor = style.borderBottomColor, borderBottomStyle = style.borderBottomStyle, borderBottomWidth = style.borderBottomWidth, borderRightWidth = style.borderRightWidth, borderLeftWidth = style.borderLeftWidth; var c0 = rbl * (1.0 - KAPPA); var c1 = rbr * (1.0 - KAPPA); ctx.moveTo(left + width, top + height - rbr); ctx.bezierCurveTo(left + width, top + height - c1, left + width - c1, top + height, left + width - rbr, top + height); ctx.lineTo(left + rbl, top + height); ctx.bezierCurveTo(left + c0, top + height, left, top + height - c0, left, top + height - rbl); ctx.strokeColor(borderBottomColor); ctx.lineWidth(Math.max(borderBottomWidth, borderRightWidth, borderLeftWidth) * 2); if (borderBottomStyle === 'dashed') { ctx.dash(borderBottomWidth * 2, { space: borderBottomWidth * 1.2 }); } else if (borderBottomStyle === 'dotted') { ctx.dash(borderBottomWidth, { space: borderBottomWidth * 1.2 }); } ctx.stroke(); ctx.undash(); }; var clipBorderLeft = function clipBorderLeft(ctx, layout, style, rbl, rtl) { var top = layout.top, left = layout.left, width = layout.width, height = layout.height; var borderTopWidth = style.borderTopWidth, borderLeftWidth = style.borderLeftWidth, borderBottomWidth = style.borderBottomWidth; // Clip outer left border edge ctx.moveTo(left, top + height - rbl); ctx.lineTo(left, top + rtl); // Ellipse coefficients outer top left cap var c0 = rtl * (1.0 - KAPPA); // Clip outer top left cap ctx.bezierCurveTo(left, top + c0, left + c0, top, left + rtl, top); // Move right in case the margin exceedes the radius var topLeftCoordX = left + Math.max(borderLeftWidth, rtl); ctx.lineTo(topLeftCoordX, top); // Clip inner top left cap ctx.lineTo(topLeftCoordX, top + borderTopWidth); // Ellipse coefficients inner top left cap var innerTopLeftRadiusX = Math.max(rtl - borderLeftWidth, 0); var innerTopLeftRadiusY = Math.max(rtl - borderTopWidth, 0); var c1 = innerTopLeftRadiusX * (1.0 - KAPPA); var c2 = innerTopLeftRadiusY * (1.0 - KAPPA); // Clip inner top right cap ctx.bezierCurveTo(left + borderLeftWidth + c1, top + borderTopWidth, left + borderLeftWidth, top + borderTopWidth + c2, left + borderLeftWidth, top + Math.max(rtl, borderTopWidth)); // Clip inner left border edge ctx.lineTo(left + borderLeftWidth, top + height - Math.max(rbl, borderBottomWidth)); // Ellipse coefficients inner bottom left cap var innerBottomLeftRadiusX = Math.max(rbl - borderLeftWidth, 0); var innerBottomLeftRadiusY = Math.max(rbl - borderBottomWidth, 0); var c3 = innerBottomLeftRadiusX * (1.0 - KAPPA); var c4 = innerBottomLeftRadiusY * (1.0 - KAPPA); var bottomLeftXCoord = left + Math.max(rbl, borderLeftWidth); // Clip inner top left cap ctx.bezierCurveTo(left + borderLeftWidth, top + height - borderBottomWidth - c4, left + borderLeftWidth + c3, top + height - borderBottomWidth, bottomLeftXCoord, top + height - borderBottomWidth); ctx.lineTo(bottomLeftXCoord, top + height); // Move left in case the margin exceedes the radius ctx.lineTo(left + rbl, top + height); // Ellipse coefficients outer top right cap var c5 = rbl * (1.0 - KAPPA); // Clip outer top right cap ctx.bezierCurveTo(left + c5, top + height, left, top + height - c5, left, top + height - rbl); ctx.closePath(); ctx.clip(); // Clip border right cap joins if (borderBottomWidth) { var trSlope = -borderBottomWidth / borderLeftWidth; ctx.moveTo(le