mirror of
https://github.com/niklasvh/html2canvas.git
synced 2023-08-10 21:13:10 +03:00
added support for rendering ordered lists and list-style
This commit is contained in:
parent
4551976246
commit
7335984ab7
@ -77,6 +77,7 @@
|
|||||||
"homepage": "https://html2canvas.hertzen.com",
|
"homepage": "https://html2canvas.hertzen.com",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"punycode": "2.1.0"
|
"punycode": "2.1.0",
|
||||||
|
"liststyletype-formatter": "latest"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
146
src/ListItem.js
Normal file
146
src/ListItem.js
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
/* @flow */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import type {BackgroundSource} from './parsing/background';
|
||||||
|
import type ResourceLoader from './ResourceLoader';
|
||||||
|
|
||||||
|
import {parseBackgroundImage} from './parsing/background';
|
||||||
|
import {copyCSSStyles, getParentOfType} from './Util';
|
||||||
|
import NodeContainer from './NodeContainer';
|
||||||
|
import TextContainer from './TextContainer';
|
||||||
|
import ListStyleTypeFormatter from 'liststyletype-formatter';
|
||||||
|
|
||||||
|
// Margin between the enumeration and the list item content
|
||||||
|
const MARGIN_RIGHT = 7;
|
||||||
|
|
||||||
|
export const LIST_STYLE_POSITION = {
|
||||||
|
INSIDE: 0,
|
||||||
|
OUTSIDE: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ListStylePosition = $Values<typeof LIST_STYLE_POSITION>;
|
||||||
|
|
||||||
|
export type ListStyle = {
|
||||||
|
listStyleType: string,
|
||||||
|
listStyleImage: BackgroundSource,
|
||||||
|
listStylePosition: ListStylePosition
|
||||||
|
};
|
||||||
|
|
||||||
|
export const parseListStyle = (style: CSSStyleDeclaration): ListStyle => {
|
||||||
|
const listStyleImage = parseBackgroundImage(style.getPropertyValue('list-style-image'));
|
||||||
|
return {
|
||||||
|
listStyleType: style.getPropertyValue('list-style-type'),
|
||||||
|
listStyleImage: listStyleImage && listStyleImage[0],
|
||||||
|
listStylePosition: parseListStylePosition(style.getPropertyValue('list-style-position'))
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const parseListStylePosition = (position: string): ListStylePosition => {
|
||||||
|
switch (position) {
|
||||||
|
case 'inside':
|
||||||
|
return LIST_STYLE_POSITION.INSIDE;
|
||||||
|
case 'outside':
|
||||||
|
return LIST_STYLE_POSITION.OUTSIDE;
|
||||||
|
}
|
||||||
|
return LIST_STYLE_POSITION.OUTSIDE;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getListItemValue = (node: HTMLLIElement): number => {
|
||||||
|
if (node.value) {
|
||||||
|
return node.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const listContainer = getParentOfType(node, ['OL', 'UL']);
|
||||||
|
if (!listContainer || listContainer.tagName === 'UL') {
|
||||||
|
// The actual value isn't needed for unordered lists, just return an arbitrary value
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// $FlowFixMe
|
||||||
|
let value = listContainer.start !== undefined ? listContainer.start - 1 : 0;
|
||||||
|
const listItems = listContainer.querySelectorAll('li');
|
||||||
|
const lenListItems = listItems.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < lenListItems; i++) {
|
||||||
|
// $FlowFixMe
|
||||||
|
const listItem: HTMLLIElement = listItems[i];
|
||||||
|
if (getParentOfType(listItem, ['OL']) === listContainer) {
|
||||||
|
value = listItem.hasAttribute('value') ? listItem.value : value + 1;
|
||||||
|
}
|
||||||
|
if (listItem === node) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const inlineListItemElement = (
|
||||||
|
node: HTMLLIElement,
|
||||||
|
container: NodeContainer,
|
||||||
|
resourceLoader: ResourceLoader
|
||||||
|
): void => {
|
||||||
|
const style = node.ownerDocument.defaultView.getComputedStyle(node, null);
|
||||||
|
const listStyle = parseListStyle(style);
|
||||||
|
|
||||||
|
if (listStyle.listStyleType === 'none') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapper = node.ownerDocument.createElement('html2canvaswrapper');
|
||||||
|
copyCSSStyles(style, wrapper);
|
||||||
|
|
||||||
|
wrapper.style.position = 'fixed';
|
||||||
|
wrapper.style.bottom = 'auto';
|
||||||
|
|
||||||
|
switch (listStyle.listStylePosition) {
|
||||||
|
case LIST_STYLE_POSITION.OUTSIDE:
|
||||||
|
wrapper.style.left = 'auto';
|
||||||
|
wrapper.style.right = `${node.ownerDocument.defaultView.innerWidth -
|
||||||
|
container.bounds.left +
|
||||||
|
MARGIN_RIGHT}px`;
|
||||||
|
wrapper.style.textAlign = 'right';
|
||||||
|
break;
|
||||||
|
case LIST_STYLE_POSITION.INSIDE:
|
||||||
|
wrapper.style.left = `${container.bounds.left}px`;
|
||||||
|
wrapper.style.right = 'auto';
|
||||||
|
wrapper.style.textAlign = 'left';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let text;
|
||||||
|
if (listStyle.listStyleImage && listStyle.listStyleImage !== 'none') {
|
||||||
|
if (listStyle.listStyleImage.method === 'url') {
|
||||||
|
const image = node.ownerDocument.createElement('img');
|
||||||
|
image.src = listStyle.listStyleImage.args[0];
|
||||||
|
wrapper.style.top = `${container.bounds.top}px`;
|
||||||
|
wrapper.style.width = 'auto';
|
||||||
|
wrapper.style.height = 'auto';
|
||||||
|
wrapper.appendChild(image);
|
||||||
|
} else {
|
||||||
|
const size = parseFloat(container.style.font.fontSize) * 0.5;
|
||||||
|
wrapper.style.top = `${container.bounds.top + container.bounds.height - 1.5 * size}px`;
|
||||||
|
wrapper.style.width = `${size}px`;
|
||||||
|
wrapper.style.height = `${size}px`;
|
||||||
|
wrapper.style.backgroundImage = style.listStyleImage;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
text = node.ownerDocument.createTextNode(
|
||||||
|
ListStyleTypeFormatter.format(getListItemValue(node), style.listStyleType)
|
||||||
|
);
|
||||||
|
wrapper.appendChild(text);
|
||||||
|
wrapper.style.top = `${container.bounds.top}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// $FlowFixMe
|
||||||
|
const body: HTMLBodyElement = node.ownerDocument.body;
|
||||||
|
body.appendChild(wrapper);
|
||||||
|
|
||||||
|
if (text) {
|
||||||
|
container.childNodes.push(TextContainer.fromTextNode(text, container));
|
||||||
|
body.removeChild(wrapper);
|
||||||
|
} else {
|
||||||
|
// $FlowFixMe
|
||||||
|
container.childNodes.push(new NodeContainer(wrapper, container, resourceLoader, 0));
|
||||||
|
}
|
||||||
|
};
|
@ -6,6 +6,7 @@ import StackingContext from './StackingContext';
|
|||||||
import NodeContainer from './NodeContainer';
|
import NodeContainer from './NodeContainer';
|
||||||
import TextContainer from './TextContainer';
|
import TextContainer from './TextContainer';
|
||||||
import {inlineInputElement, inlineTextAreaElement, inlineSelectElement} from './Input';
|
import {inlineInputElement, inlineTextAreaElement, inlineSelectElement} from './Input';
|
||||||
|
import {inlineListItemElement} from './ListItem';
|
||||||
|
|
||||||
export const NodeParser = (
|
export const NodeParser = (
|
||||||
node: HTMLElement,
|
node: HTMLElement,
|
||||||
@ -71,6 +72,9 @@ const parseNodeTree = (
|
|||||||
} else if (childNode.tagName === 'SELECT') {
|
} else if (childNode.tagName === 'SELECT') {
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
inlineSelectElement(childNode, container);
|
inlineSelectElement(childNode, container);
|
||||||
|
} else if (childNode.tagName === 'LI') {
|
||||||
|
// $FlowFixMe
|
||||||
|
inlineListItemElement(childNode, container, resourceLoader);
|
||||||
}
|
}
|
||||||
|
|
||||||
const SHOULD_TRAVERSE_CHILDREN = childNode.tagName !== 'TEXTAREA';
|
const SHOULD_TRAVERSE_CHILDREN = childNode.tagName !== 'TEXTAREA';
|
||||||
|
18
src/Util.js
18
src/Util.js
@ -17,5 +17,23 @@ export const copyCSSStyles = (style: CSSStyleDeclaration, target: HTMLElement):
|
|||||||
return target;
|
return target;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getParentOfType = (node: HTMLElement, parentTypes: Array<string>): ?HTMLElement => {
|
||||||
|
let parent = node.parentNode;
|
||||||
|
if (!parent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// $FlowFixMe
|
||||||
|
while (parentTypes.indexOf(parent.tagName) < 0) {
|
||||||
|
parent = parent.parentNode;
|
||||||
|
if (!parent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// $FlowFixMe
|
||||||
|
return parent;
|
||||||
|
};
|
||||||
|
|
||||||
export const SMALL_IMAGE =
|
export const SMALL_IMAGE =
|
||||||
'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
||||||
|
75
tests/reftests/list/liststyle.html
Normal file
75
tests/reftests/list/liststyle.html
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>List tests</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<script type="text/javascript" src="../../test.js"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.list1 {
|
||||||
|
list-style-type: circle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list2 {
|
||||||
|
list-style-image: url(../../assets/image.jpg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list3 {
|
||||||
|
list-style-image: linear-gradient(60deg, deeppink, aquamarine);
|
||||||
|
list-style-position: inside;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list4 {
|
||||||
|
}
|
||||||
|
|
||||||
|
.list5 {
|
||||||
|
list-style-type: lower-roman;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list6 {
|
||||||
|
list-style-type: hiragana;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list7 {
|
||||||
|
list-style-type: simp-chinese-informal;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<ul class="list1">
|
||||||
|
<li>Alpha</li>
|
||||||
|
<li>Beta</li>
|
||||||
|
<li>Gamma</li>
|
||||||
|
</ul>
|
||||||
|
<ul class="list2">
|
||||||
|
<li>Alpha</li>
|
||||||
|
<li>Beta</li>
|
||||||
|
<li>Gamma</li>
|
||||||
|
</ul>
|
||||||
|
<ul class="list3">
|
||||||
|
<li>Alpha</li>
|
||||||
|
<li>Beta</li>
|
||||||
|
<li>Gamma</li>
|
||||||
|
</ul>
|
||||||
|
<ol class="list4" start="-1">
|
||||||
|
<li>Alpha</li>
|
||||||
|
<li>Beta</li>
|
||||||
|
<li>Gamma</li>
|
||||||
|
</ol>
|
||||||
|
<ol class="list5">
|
||||||
|
<li>Alpha</li>
|
||||||
|
<li value="5">Beta</li>
|
||||||
|
<li>Gamma</li>
|
||||||
|
</ol>
|
||||||
|
<ol class="list6">
|
||||||
|
<li>Alpha</li>
|
||||||
|
<li>Beta</li>
|
||||||
|
<li>Gamma</li>
|
||||||
|
</ol>
|
||||||
|
<ol class="list7">
|
||||||
|
<li>Alpha</li>
|
||||||
|
<li>Beta</li>
|
||||||
|
<li>Gamma</li>
|
||||||
|
</ol>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user