Compare commits

..

82 Commits

Author SHA1 Message Date
yuanchuan dffd83078b Add experimental once function 2023-02-19 12:30:17 +08:00
yuanchuan 787bb390c0 Change cycle shift direction 2023-02-19 09:27:11 +08:00
yuanchuan 54077cb530 0.34.1 2023-02-15 09:38:01 +08:00
yuanchuan f0c6bd2fa5 Fix gap property break and remove redundant styles 2023-02-14 20:58:54 +08:00
yuanchuan 1b69cf9a17 0.34.0 2023-02-14 14:23:56 +08:00
yuanchuan 162b1c7a13 Gerenate new seed only once 2023-02-14 09:43:32 +08:00
yuanchuan f415b9c9e0 Set seed value correctly 2023-02-14 06:30:54 +08:00
yuanchuan c3173271a2 Revert "Fallback contain to overflow"
This reverts commit 768f4bd750.
2023-02-13 20:29:06 +08:00
yuanchuan 768f4bd750 Fallback contain to overflow 2023-02-13 20:27:27 +08:00
yuanchuan d40fd27a94 Add dx and dy distance functions 2023-02-13 20:01:30 +08:00
yuanchuan df4c69ecc1 Fix CSS parser for statement break 2023-02-13 15:38:59 +08:00
yuanchuan 673410119f Enable auto aspect-ratio to cells 2023-02-13 07:58:45 +08:00
yuanchuan f6366ab07c Add gap property 2023-02-12 23:26:29 +08:00
yuanchuan ccff4647da Add rotate, scale and flex options for grid property 2023-02-12 23:18:32 +08:00
yuanchuan 114d7d72f2 Support 0 index value in m function 2023-02-12 10:50:31 +08:00
yuanchuan e97a38c442 Fix parsing function composition 2023-02-12 10:46:18 +08:00
yuanchuan bc6edb5951 Add no-clip keyword for grid property 2023-02-12 09:41:41 +08:00
yuanchuan b7d3405708 Readd contain property for doodle element 2023-02-12 05:55:57 +08:00
yuanchuan 772a561b9e Extend grid property to support aspect-ratio 2023-02-11 22:51:51 +08:00
yuanchuan 21fa24cd3e Extend grid propery to support doodle background color 2023-02-11 20:05:04 +08:00
yuanchuan 57fc20d331 0.33.1 2023-02-08 22:32:16 +08:00
yuanchuan 7978410f4a Fix `@pn` index value inside `@m` function 2023-02-08 22:22:52 +08:00
yuanchuan 1c3d4b8905 Fix svg function detection 2023-02-08 18:38:48 +08:00
yuanchuan 17f7309463 0.33.0 2023-02-04 20:35:03 +08:00
yuanchuan e6cbd41e3a Add SVG playground link 2023-02-04 20:33:30 +08:00
yuanchuan 932bcfadb4 Add expand alias for padding because I can't tell which one is better 2023-02-04 18:35:00 +08:00
yuanchuan 1cc4ed63a9 Ignore invalid viewBox value 2023-02-04 18:02:27 +08:00
yuanchuan db9aa9ffd3 Support padding in SVG viewBox value.
For example:

  svg {
    viewBox: 0 0 1 1 padding .1
  }
2023-02-04 16:41:29 +08:00
yuanchuan dfc6fdd932 Check base to prevent NaN values 2023-01-08 08:44:51 +08:00
yuanchuan 6c783dff4b Add Svg function which returns raw svg code 2023-01-08 08:20:30 +08:00
yuanchuan 5b72fd34d3 Add svg-pattern function 2023-01-08 08:11:58 +08:00
yuanchuan 902a8788cd Do not use `??` operator at the moment.
Fix #110
2023-01-05 22:45:47 +08:00
yuanchuan 792397c033 0.32.1 2023-01-05 06:42:41 +08:00
yuanchuan 1a7a383944 Fix css parser for reading tail function names 2023-01-05 06:05:23 +08:00
yuanchuan 1783e6572d Build grid without DOM object 2022-12-27 21:36:29 +08:00
yuanchuan 277ca2b1a8 0.32.0 2022-12-17 13:42:29 +08:00
yuanchuan a548f2f907 Reduce imports of the exported svg function 2022-12-17 13:02:55 +08:00
yuanchuan fb9e96dcd9 Add `@unicode` function to insert unicode elegantly. 2022-12-17 12:53:42 +08:00
yuanchuan 7393101214 Fix parsing quotes in content 2022-12-17 12:04:00 +08:00
yuanchuan dd38c4dd45 Add shorthand to calc index values
For example:

@i5 === @calc(@i + 5)
@i(*10) === @calc(@i * 10)
@i(-10) === @calc(@i - 10)
@i(/10) === @calc(@i / 10)
@i(%10) === @calc(@i % 10)
@i(%10) === @calc(@i % 10)

@i(10/) === @calc(10 / @i)
@i(10-) === @calc(10 - @i)
2022-12-17 08:47:02 +08:00
yuanchuan 0ae460d6b7 Add `@mirror` and `@Mirror` function 2022-12-16 21:18:08 +08:00
yuanchuan 2e206c48e7 Improve `@cycle` to support comma-separated values 2022-12-13 10:18:18 +08:00
yuanchuan 27a8d2c8be Rebuild grid correctly 2022-12-13 09:21:02 +08:00
yuanchuan 704eebccf9 Fix svg-extended style 2022-12-11 19:25:40 +08:00
yuanchuan 166b53546d Fix bad reverse function implementation 2022-12-11 13:50:43 +08:00
yuanchuan d2e1144c39 0.31.0 2022-12-11 13:11:55 +08:00
yuanchuan 5bab81b8cc Fix function name detection 2022-12-11 13:11:43 +08:00
yuanchuan a31a652fbe Add `@content` property for textnode content 2022-12-11 12:47:24 +08:00
yuanchuan 9b72bf9b30 The `@` symbol can be ommited if the functions are chained together 2022-12-11 10:41:58 +08:00
yuanchuan c01f3b5d29 Patch reverse function to support both SVG path commands and general
arguments
2022-12-11 10:21:16 +08:00
yuanchuan a5c16bc862 Fix style tag parsing for complex css selectors 2022-12-11 09:50:07 +08:00
yuanchuan 82449124ed Skip variable properties 2022-12-05 21:56:42 +08:00
yuanchuan ca11666be3 I'm not sure which function names are better so I added a `@pnr` alias for `@pr`. 2022-12-02 17:17:58 +08:00
yuanchuan 61278b715d 0.30.9 2022-12-02 16:36:12 +08:00
yuanchuan 007f705615 Simplify image detection 2022-12-02 11:45:00 +08:00
yuanchuan 3831fd24c4 Use single rotate for inner `@place` transformation 2022-12-02 11:11:29 +08:00
yuanchuan a31e596bc0 Use `@pn` and `@position` as aliases 2022-12-02 10:17:42 +08:00
yuanchuan 6293f3f5d9 Compute index instead of reversing the whole array 2022-12-02 08:53:13 +08:00
yuanchuan c7ac4e4372 Support variables in standalone SVG export function 2022-12-02 08:38:11 +08:00
yuanchuan b038d3452e 0.30.8 2022-11-21 16:28:52 +08:00
yuanchuan 60fcc89df1 Fix commas 2022-11-21 16:21:36 +08:00
yuanchuan 37e3023cad Add pr function for picking from right to left
Also add an alias pl to pn
2022-11-21 16:08:50 +08:00
yuanchuan b9edb539b4 0.30.7 2022-11-11 14:25:09 +08:00
yuanchuan ff888cbb0d Add cycle function 2022-11-11 14:15:35 +08:00
yuanchuan a51db4e906 0.30.6 2022-10-31 18:28:08 +08:00
yuanchuan 5643b53a3d I was wrong. Only set `background-size` to `pattern`, `canvas`, and `shader`. 2022-10-31 18:21:17 +08:00
yuanchuan 8269704dc9 0.30.5 2022-10-31 17:22:11 +08:00
yuanchuan ecdbda2e0d Always add `background-size:100%` to background properties 2022-10-31 17:17:25 +08:00
yuanchuan c93d227daa 0.30.4 2022-10-17 15:40:11 +08:00
yuanchuan 5696cf112d Normalize quoted attribute values 2022-10-12 16:47:28 +08:00
yuanchuan e5b13f1a0f 0.30.3 2022-10-09 10:56:35 +08:00
yuanchuan be9fb9a3bd Fix SVG generator on cutting values 2022-10-09 10:53:56 +08:00
yuanchuan 8ce6786020 Do not break for decodeURI 2022-10-08 10:47:09 +08:00
yuanchuan 4ff1271086 0.30.2 2022-10-05 20:36:40 +08:00
yuanchuan 1ba7ddbdc9 Prevent type error 2022-10-05 20:15:52 +08:00
yuanchuan 0ca2f12646 0.30.1 2022-10-05 13:16:14 +08:00
yuanchuan 62ecf66d5d Improve CSS and SVG parser on quotes 2022-10-05 13:08:08 +08:00
yuanchuan b3eb97a556 Fix tail parens 2022-10-04 22:55:54 +08:00
yuanchuan 3500881a62 Remove unnecessary part of patterns 2022-10-04 19:48:11 +08:00
yuanchuan 591ca51c3e Skip evaluation for specific patterns 2022-10-04 19:45:53 +08:00
yuanchuan 68f7ace62c Add range support for m function
resolve #109
2022-10-04 14:53:21 +08:00
yuanchuan c0d3668e16 Increase range limit 2022-10-04 14:09:10 +08:00
20 changed files with 1676 additions and 400 deletions

View File

@ -1,3 +1,361 @@
## 0.34.1
* Fix gap property break and remove redundant styles.
<br /> <br />
## 0.34.0
### Breaking
* The css-doodle element now is by default restrict the visible area to its dimension,
which means it's overflow hidden. There are two ways to change this behavior:
1. Append the <code>noclip</code> keyword to the `@grid` property:
```css
@grid: 1 / 400px noclip;
```
2. Overide the `contain` property:
```css
:doodle {
@grid: 1 / 400px;
contain: none;
}
```
### Features
* Extended `@grid` to support doodle background color, after the second `/` symbol.
```css
@grid: 1 / 400px / #000;
```
* Extended `@grid` and `@size` to support <code>aspect-ratio</code> if one of the `width` and `width` is `auto`.
```css
@grid: 1 / 400px auto 2;
@grid: 1 / 400px auto (4/3);
@size: 100px auto 1.5;
```
* Extended `@grid` to support <code>rotate</code> and <code>scale</code> to <code>:container</code>.
```css
@grid: 1 / 400px + 2; /* + means scale */
@grid: 1 / 400px * 45deg; /* * means scale */
@grid: 1 / 400px + 2 * 45deg;
```
* Extended `@grid` to change the layout to `Flex` layout.
```css
@grid: - 8x2 / 400px; /* - means using flex */
@grid: | 8x2 / 400px; /* | means using flex and vertical direction */
```
* Added support for 0 index value in `@m` function.
```css
/* 0 1 2 3 4 */
@content: @M0-4.n
```
* Added `@gap` property.
```css
@grid: 10 / 400px;
@gap: 1px;
```
* Added `@dx` and `@dy` functions.
```css
/* -2,-2 -1,-2 0,-2 1,-2 2,-2
-2,-1 -1,-1 0,-1 1,-1 2,-1
-2,0 -1,0 0,0 1,0 2,0
-2,1 -1,1 0,1 1,1 2,1
-2,2 -1,2 0,2 1,2 2,2 */
@grid: 5 / 400px;
@content: @dx, @dy;
```
Adjust offset.
```css
/* -3,-3 -2,-3 -1,-3 0,-3 1,-3
-3,-2 -2,-2 -1,-2 0,-2 1,-2
-3,-1 -2,-1 -1,-1 0,-1 1,-1
-3,0 -2,0 -1,0 0,0 1,0
-3,1 -2,1 -1,1 0,1 1,1 */
@grid: 5 / 400px;
@content: @dx(1), @dy(1);
```
### Fixes
* Fixed CSS parsing on function composition.
* Fixed seed value at pre-compose phase.
<br /> <br />
## 0.33.1
* Fixed `@Svg` function detection for transforming the `multiplication` feature.
* Fixed `@pn` index value inside `@m` function.
<br /> <br />
## 0.33.0
* Added new `@svg-pattern` function to reduce boilerplate code.
```css
background: @svg-pattern(
width, height: 10%;
viewBox: 0 0 10 10;
circle { }
);
```
* Added new `@Svg` function to return raw SVG code rather than encoded URI value.
```css
@content: @Svg(
viewBox: -1 -1 2 2;
circle {
r: 1;
}
);
```
* Added `padding` value for SVG `viewBox` property.
```css
viewBox: -1 -1 2 2 padding 1;
/* equals to */
viewBox: -2 2 4 4;
```
<br /> <br />
## 0.32.2
* Do not use `??` operator at the moment.
<br /> <br />
## 0.32.1
* Build grid without DOM object.
* Fix CSS parser for reading tail function names.
<br /> <br />
## 0.32.0
### Features
* Add `@unicode` function to insert Unicode and it can be used both in HTML and CSS.
```css
/* in HTML */
@content: @unicode(0x2500);
/* in CSS */
:after {
content: @unicode(0x2500);
}
```
A sequence of Unicode characters.
```css
@content: @pn.unicode(0x2500, 0x257f, 0x2588);
```
* Add `@mirror/@Mirror` function to transform input items, simlar to `@cycle` and `@reverse`.
```css
/* 1, 2, 3, 4, 5, 5, 4, 3, 2, 1 */
@mirror(1, 2, 3, 4, 5);
/* 1, 2, 3, 4, 5, 4, 3, 2, 1 */
@Mirror(1, 2, 3, 4, 5);
```
* All index functions, `@i/I`, `@x/X`, `@y/Y`, and `@n/N/nx/ny` accept extra arguments to do calulations.
```css
@i5 === @calc(@i + 5)
@i(*10) === @calc(@i * 10)
@i(-10) === @calc(@i - 10)
@i(/10) === @calc(@i / 10)
@i(%10) === @calc(@i % 10)
@i(10/) === @calc(10 / @i)
@i(10-) === @calc(10 - @i)
```
### Patches
* Reduce imports of the exported svg function.
* Improve `@cycle` to support comma-separated values.
* Fix grid build for `@content`.
* Fix parsing quotes in content.
```css
/* There used to be bugs` */
content: '");';
```
<br /> <br />
## 0.31.2
* Fix svg-extended style tag generation.
<br /> <br />
## 0.31.1
* Fix bad reverse function implementation.
<br /> <br />
## 0.31.0
* Add `@content` property for text content so that they can be selectable.
* The `@` symbol can be ommited if the functions are chained together.
* Patch reverse function to support both SVG path commands and general arguments.
* Skip variable properties for SVG generation.
* Fix style tag parsing inside SVG for complex CSS selectors.
<br /> <br />
## 0.30.10
* Add `@pnr` alias for `@pr`.
<br /> <br />
## 0.30.9
* Support variables in standalone SVG export function.
* Compute index instead of reversing the whole array.
* Use single rotate for inner `@place` transformation.
* Simplify image detection.
* Use `@pn` and `@position` as aliases.
<br /> <br />
## 0.30.8
Experimenting the new added `pr` function.
<br /> <br />
## 0.30.7
Experimenting the new added `cycle()` function.
<br /> <br />
## 0.30.6
I was wrong. Only set `background-size:100%` to `pattern`, `canvas`, and `shader`.
<br /> <br />
## 0.30.5
* Always add `background-size:100%` to background properties.
<br /> <br />
## 0.30.4
* Normalize quoted SVG attribute values.
<br /> <br />
## 0.30.3
* Do not break for decodeURI.
* Fix SVG generator on cutting values.
<br /> <br />
## 0.30.2
* Fix type error.
<br /> <br />
## 0.30.1
* Fix CSS and SVG parser on quotes and parens.
* Add range support for `@m` function.
```css
::after {
/* 5 4 3 2 1 */
content: @M5-1.@n;
}
```
<br /> <br />
## 0.30.0 ## 0.30.0
* Add exports. * Add exports.
@ -30,6 +388,9 @@
`); `);
``` ```
<br /> <br />
## 0.29.2 ## 0.29.2

View File

@ -28,6 +28,7 @@ A web component for drawing patterns with CSS.
* [Tabbied](https://tabbied.com) -- Doodle with generated patterns * [Tabbied](https://tabbied.com) -- Doodle with generated patterns
* [Shapes](https://css-doodle.com/shapes) -- Discover new CSS polygon shapes * [Shapes](https://css-doodle.com/shapes) -- Discover new CSS polygon shapes
* [SVG playground](https://css-doodle.com/svg) -- Generate SVG code with new syntax
## Resources ## Resources

File diff suppressed because it is too large Load Diff

2
css-doodle.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{ {
"name": "css-doodle", "name": "css-doodle",
"version": "0.30.0", "version": "0.34.1",
"description": "A web component for drawing patterns with CSS", "description": "A web component for drawing patterns with CSS",
"main": "css-doodle.js", "main": "css-doodle.js",
"scripts": { "scripts": {

View File

@ -1,14 +1,24 @@
import { generate_css } from '../../generator/css.js'; import { generate_css } from '../../generator/css.js';
import parse_css from './../../parser/parse-css.js'; import parse_css from './../../parser/parse-css.js';
import parse_grid from './../../parser/parse-grid.js'; import parse_svg from './../../parser/parse-svg.js';
import { make_tag_function } from '../../utils/index.js';
export default function svg(rules) { export default function svg(rules) {
let parsedSvg = parse_svg(rules);
let inlineVariable = '';
for (let item of parsedSvg.value) {
if (item.variable) {
inlineVariable += `${item.name}: ${item.value};`;
}
}
let result = generate_css( let result = generate_css(
parse_css(`background: @svg(${rules});`), parse_css(`${inlineVariable} background: @svg(${rules});`),
parse_grid('1x1') { x: 1, y: 1, z: 1, count: 1, ratio: 1 }
); );
let raw = result && result.styles && result.styles.cells || ''; let raw = result && result.styles && result.styles.cells || '';
let cut = raw.substring(52, raw.length - 5); let cut = raw.substring(raw.indexOf('%3'), raw.lastIndexOf('");'));
return decodeURIComponent(cut); try {
return decodeURIComponent(cut);
} catch (e) {
return '';
}
} }

View File

@ -1,7 +1,7 @@
import { create_svg_url, normalize_svg } from './utils/svg.js'; import { create_svg_url, normalize_svg } from './utils/svg.js';
import { generate_svg } from './generator/svg.js'; import { generate_svg } from './generator/svg.js';
import { cell_id, is_letter, is_nil, add_alias, unique_id, lerp } from './utils/index.js'; import { cell_id, is_letter, is_nil, is_empty, add_alias, unique_id, lerp } from './utils/index.js';
import { lazy, clamp, sequence, get_value } from './utils/index.js'; import { lazy, clamp, sequence, get_value } from './utils/index.js';
import { by_unit, by_charcode } from './utils/transform.js'; import { by_unit, by_charcode } from './utils/transform.js';
import { last } from './utils/list.js'; import { last } from './utils/list.js';
@ -26,7 +26,7 @@ function make_sequence(c) {
if (!actions || !n) return ''; if (!actions || !n) return '';
let count = get_value(n()); let count = get_value(n());
let evaluated = count; let evaluated = count;
if (/\D/.test(count)){ if (/\D/.test(count) && !/\d+[x-]\d+/.test(count)) {
evaluated = calc(count); evaluated = calc(count);
if (evaluated === 0) { if (evaluated === 0) {
evaluated = count; evaluated = count;
@ -61,62 +61,107 @@ function map2d(value, min, max, amp = 1) {
return lerp((value - ma) / (mb - ma), min * amp, max * amp); return lerp((value - ma) / (mb - ma), min * amp, max * amp);
} }
function calc_with(base) {
return v => {
if (is_empty(v) || is_empty(base)) {
return base;
}
if (/^[+*-\/%][.\d\s]/.test(v)) {
let op = v[0];
let num = Number(v.substr(1).trim()) || 0;
switch (op) {
case '+': return base + num;
case '-': return base - num;
case '*': return base * num;
case '/': return base / num;
case '%': return base % num;
}
}
else if (/[+*-\/%]$/.test(v)) {
let op = v.substr(-1);
let num = Number(v.substr(0, v.length - 1).trim()) || 0;
switch (op) {
case '+': return num + base;
case '-': return num - base;
case '*': return num * base;
case '/': return num / base;
case '%': return num % base;
}
}
return base + (Number(v) || 0);
}
}
const Expose = add_alias({ const Expose = add_alias({
i({ count }) { i({ count }) {
return _ => count; return calc_with(count);
}, },
y({ y }) { y({ y }) {
return _ => y; return calc_with(y);
}, },
x({ x }) { x({ x }) {
return _ => x; return calc_with(x);
}, },
z({ z }) { z({ z }) {
return _ => z; return calc_with(z);
}, },
I({ grid }) { I({ grid }) {
return _ => grid.count; return calc_with(grid.count);
}, },
Y({ grid }) { Y({ grid }) {
return _ => grid.y; return calc_with(grid.y);
}, },
X({ grid }) { X({ grid }) {
return _ => grid.x; return calc_with(grid.x);
}, },
Z({ grid }) { Z({ grid }) {
return _ => grid.z; return calc_with(grid.z);
}, },
id({ x, y, z }) { id({ x, y, z }) {
return _ => cell_id(x, y, z); return _ => cell_id(x, y, z);
}, },
dx({ x, grid }) {
return n => {
n = Number(n) || 0;
return x - .5 - n - grid.x / 2;
}
},
dy({ y, grid }) {
return n => {
n = Number(n) || 0;
return y - .5 - n - grid.y / 2;
}
},
n({ extra }) { n({ extra }) {
let lastExtra = last(extra); let lastExtra = last(extra);
return n => lastExtra ? (lastExtra[0] + (Number(n) || 0)) : '@n'; return lastExtra ? calc_with(lastExtra[0]) : '@n';
}, },
nx({ extra }) { nx({ extra }) {
let lastExtra = last(extra); let lastExtra = last(extra);
return n => lastExtra ? (lastExtra[1] + (Number(n) || 0)) : '@nx'; return lastExtra ? calc_with(lastExtra[1]) : '@nx';
}, },
ny({ extra }) { ny({ extra }) {
let lastExtra = last(extra); let lastExtra = last(extra);
return n => lastExtra ? (lastExtra[2] + (Number(n) || 0)) : '@ny'; return lastExtra ? calc_with(lastExtra[2]) : '@ny';
}, },
N({ extra }) { N({ extra }) {
let lastExtra = last(extra); let lastExtra = last(extra);
return n => lastExtra ? (lastExtra[3] + (Number(n) || 0)) : '@N'; return lastExtra ? calc_with(lastExtra[3]) : '@N';
}, },
m: make_sequence(','), m: make_sequence(','),
@ -167,21 +212,38 @@ const Expose = add_alias({
}); });
}, },
pn({ context, extra, position }) { pl({ context, extra, position }) {
let lastExtra = last(extra); let lastExtra = last(extra);
let sig = lastExtra ? last(lastExtra) : ''; let sig = lastExtra ? last(lastExtra) : '';
let counter = 'pn-counter' + position + sig; let counter = 'pl-counter' + position + sig;
return expand((...args) => { return expand((...args) => {
if (!context[counter]) context[counter] = 0; if (!context[counter]) context[counter] = 0;
context[counter] += 1; context[counter] += 1;
let max = args.length; let max = args.length;
let [idx = context[counter]] = lastExtra || []; let idx = lastExtra && lastExtra[6];
if (is_nil(idx)) idx = context[counter];
let pos = (idx - 1) % max; let pos = (idx - 1) % max;
let value = args[pos]; let value = args[pos];
return push_stack(context, 'last_pick', value); return push_stack(context, 'last_pick', value);
}); });
}, },
pr({ context, extra, position }) {
let lastExtra = last(extra);
let sig = lastExtra ? last(lastExtra) : '';
let counter = 'pr-counter' + position + sig;
return expand((...args) => {
if (!context[counter]) context[counter] = 0;
context[counter] += 1;
let max = args.length;
let idx = lastExtra && lastExtra[6];
if (is_nil(idx)) idx = context[counter];
let pos = (idx - 1) % max;
let value = args[max - pos - 1];
return push_stack(context, 'last_pick', value);
});
},
pd({ context, extra, position, shuffle }) { pd({ context, extra, position, shuffle }) {
let lastExtra = last(extra); let lastExtra = last(extra);
let sig = lastExtra ? last(lastExtra) : ''; let sig = lastExtra ? last(lastExtra) : '';
@ -194,7 +256,8 @@ const Expose = add_alias({
context[values] = shuffle(args || []); context[values] = shuffle(args || []);
} }
let max = args.length; let max = args.length;
let [idx = context[counter]] = lastExtra || []; let idx = lastExtra && lastExtra[6];
if (is_nil(idx)) idx = context[counter];
let pos = (idx - 1) % max; let pos = (idx - 1) % max;
let value = context[values][pos]; let value = context[values][pos];
return push_stack(context, 'last_pick', value); return push_stack(context, 'last_pick', value);
@ -321,6 +384,15 @@ const Expose = add_alias({
return create_svg_url(svg); return create_svg_url(svg);
}), }),
Svg: lazy((_, ...args) => {
let value = args.map(input => get_value(input())).join(',');
if (!value.startsWith('<')) {
let parsed = parse_svg(value);
value = generate_svg(parsed);
}
return normalize_svg(value);
}),
filter: lazy((upstream, ...args) => { filter: lazy((upstream, ...args) => {
let values = args.map(input => get_value(input())); let values = args.map(input => get_value(input()));
let value = values.join(','); let value = values.join(',');
@ -391,6 +463,20 @@ const Expose = add_alias({
return create_svg_url(svg, id); return create_svg_url(svg, id);
}), }),
'svg-pattern': lazy((_, ...args) => {
let value = args.map(input => get_value(input())).join(',');
let parsed = parse_svg(`
viewBox: 0 0 1 1;
preserveAspectRatio: xMidYMid slice;
rect {
width, height: 100%;
fill: defs pattern { ${ value } }
}
`);
let svg = generate_svg(parsed);
return create_svg_url(svg);
}),
var() { var() {
return value => `var(${ get_value(value) })`; return value => `var(${ get_value(value) })`;
}, },
@ -539,13 +625,76 @@ const Expose = add_alias({
} }
}, },
reverse(...args) { reverse() {
return commands => { return (...args) => {
let parsed = parse_svg_path(commands); let commands = args.map(get_value);
if (!parsed.valid) return commands; let parsed = parse_svg_path(commands.join(','));
return parsed.commands.reverse().map(({ name, value }) => { if (parsed.valid) {
return name + value.join(' '); let result = [];
}).join(' '); for (let i = parsed.commands.length - 1; i >= 0; --i) {
let { name, value } = parsed.commands[i];
result.push(name + value.join(' '));
}
return result.join(' ');
}
return commands.reverse();
}
},
cycle() {
return (...args) => {
let list = [];
let separator;
if (args.length == 1) {
separator = ' ';;
list = parse_value_group(args[0], { symbol: separator });
} else {
separator = ',';
list = parse_value_group(args.map(get_value).join(separator), { symbol: separator});
}
let size = list.length - 1;
let result = [list.join(separator)];
// Just ignore the performance
for (let i = 0; i < size; ++i) {
let item = list.shift();
list.push(item);
result.push(list.join(separator));
}
return result;
}
},
mirror() {
return (...args) => {
for (let i = args.length - 1; i >= 0; --i) {
args.push(args[i]);
}
return args;
}
},
Mirror() {
return (...args) => {
for (let i = args.length - 2; i >= 0; --i) {
args.push(args[i]);
}
return args;
}
},
unicode() {
return (...args) => {
return args.map(code => String.fromCharCode(code));
}
},
once({ context, extra, position }) {
let counter = 'once-counter' + position;
return (...args) => {
if (is_nil(context[counter])) {
context[counter] = args;
}
return context[counter];
} }
}, },
@ -557,6 +706,8 @@ const Expose = add_alias({
'depth': 'z', 'depth': 'z',
'rand': 'r', 'rand': 'r',
'pick': 'p', 'pick': 'p',
'pn': 'pl',
'pnr': 'pr',
// error prone // error prone
'stripes': 'stripe', 'stripes': 'stripe',
@ -588,8 +739,8 @@ const Expose = add_alias({
'sz': 'Z', 'sz': 'Z',
'size-z': 'Z', 'size-z': 'Z',
'size-depth': 'Z', 'size-depth': 'Z',
'pick-by-turn': 'pn', 'pick-by-turn': 'pl',
'pick-n': 'pn', 'pick-n': 'pl',
'pick-d': 'pd', 'pick-d': 'pd',
'offset': 'plot', 'offset': 'plot',
'Offset': 'Plot', 'Offset': 'Plot',

View File

@ -2,11 +2,12 @@ import Func from '../function.js';
import Property from '../property.js'; import Property from '../property.js';
import Selector from '../selector.js'; import Selector from '../selector.js';
import parse_value_group from '../parser/parse-value-group.js'; import parse_value_group from '../parser/parse-value-group.js';
import { uniform_time } from '../uniforms.js';
import { prefixer } from '../utils/prefixer.js';
import calc from '../calc.js'; import calc from '../calc.js';
import { uniform_time } from '../uniforms.js';
import { seedrandom } from '../lib/seedrandom.js'; import { seedrandom } from '../lib/seedrandom.js';
import { prefixer } from '../utils/prefixer.js';
import { maybe, cell_id, is_nil, get_value, lerp, unique_id } from '../utils/index.js'; import { maybe, cell_id, is_nil, get_value, lerp, unique_id } from '../utils/index.js';
import { join, make_array, remove_empty_values } from '../utils/list.js' import { join, make_array, remove_empty_values } from '../utils/list.js'
@ -22,6 +23,10 @@ function is_special_selector(s) {
return is_host_selector(s) || is_parent_selector(s); return is_host_selector(s) || is_parent_selector(s);
} }
function is_pseudo_selecotr(s) {
return /\:before|\:after/.test(s);
}
const MathFunc = {}; const MathFunc = {};
for (let name of Object.getOwnPropertyNames(Math)) { for (let name of Object.getOwnPropertyNames(Math)) {
MathFunc[name] = () => (...args) => { MathFunc[name] = () => (...args) => {
@ -43,6 +48,7 @@ class Rules {
this.grid = null; this.grid = null;
this.seed = null; this.seed = null;
this.is_grid_defined = false; this.is_grid_defined = false;
this.is_gap_defined = false;
this.coords = []; this.coords = [];
this.doodles = {}; this.doodles = {};
this.canvas = {}; this.canvas = {};
@ -51,6 +57,7 @@ class Rules {
this.reset(); this.reset();
this.custom_properties = {}; this.custom_properties = {};
this.uniforms = {}; this.uniforms = {};
this.content = {};
} }
reset() { reset() {
@ -65,6 +72,7 @@ class Rules {
this.canvas = {}; this.canvas = {};
this.pattern = {}; this.pattern = {};
this.shaders = {}; this.shaders = {};
this.content = {};
for (let key in this.rules) { for (let key in this.rules) {
if (key.startsWith('#c')) { if (key.startsWith('#c')) {
delete this.rules[key]; delete this.rules[key];
@ -322,6 +330,29 @@ class Rules {
} }
} }
add_grid_style({ fill, clip, rotate, scale, flexRow, flexColumn }) {
if (fill) {
this.add_rule(':host', `background-color: ${fill};`);
}
if (!clip) {
this.add_rule(':host', 'contain: none;');
}
if (rotate) {
this.add_rule(':container', `rotate: ${rotate};`);
}
if (scale) {
this.add_rule(':container', `scale: ${scale};`);
}
if (flexRow) {
this.add_rule(':container', `display: flex`);
this.add_rule('cell', `flex: 1`);
}
if (flexColumn) {
this.add_rule(':container', `display: flex; flex-direction: column;`);
this.add_rule('cell', `flex: 1`);
}
}
compose_rule(token, _coords, selector) { compose_rule(token, _coords, selector) {
let coords = Object.assign({}, _coords); let coords = Object.assign({}, _coords);
let prop = token.property; let prop = token.property;
@ -400,7 +431,11 @@ class Rules {
} }
} }
if (prop === 'background' && (value.includes('shader') || value.includes('canvas') || value.includes('pattern'))) { let is_image = (
/^(background|background\-image)$/.test(prop) &&
/\$\{(canvas|shader|pattern)/.test(value)
);
if (is_image) {
rule += 'background-size: 100% 100%;'; rule += 'background-size: 100% 100%;';
} }
@ -430,6 +465,7 @@ class Rules {
case 'grid': { case 'grid': {
if (is_host_selector(selector)) { if (is_host_selector(selector)) {
rule = transformed.size || ''; rule = transformed.size || '';
this.add_grid_style(transformed);
} else { } else {
rule = ''; rule = '';
if (!this.is_grid_defined) { if (!this.is_grid_defined) {
@ -439,12 +475,27 @@ class Rules {
max_grid: coords.max_grid max_grid: coords.max_grid
}); });
this.add_rule(':host', transformed.size || ''); this.add_rule(':host', transformed.size || '');
this.add_grid_style(transformed);
} }
} }
this.grid = coords.grid; this.grid = coords.grid;
this.is_grid_defined = true; this.is_grid_defined = true;
break; break;
} }
case 'gap': {
rule = '';
if (!this.is_gap_defined) {
this.add_rule(':container', `gap: ${transformed};`);
this.is_gap_defined = true;
}
break;
}
case 'content': {
rule = '';
if (transformed !== undefined && !is_pseudo_selecotr(selector) && !is_parent_selector(selector)) {
this.content[this.compose_selector(coords)] = transformed;
}
}
case 'seed': { case 'seed': {
rule = ''; rule = '';
break; break;
@ -531,7 +582,9 @@ class Rules {
} }
} }
}); });
if (!is_nil(this.seed)) { if (is_nil(this.seed)) {
this.seed = coords.seed_value;
} else {
coords.update_random(this.seed); coords.update_random(this.seed);
} }
} }
@ -673,6 +726,7 @@ class Rules {
canvas: this.canvas, canvas: this.canvas,
pattern: this.pattern, pattern: this.pattern,
uniforms: this.uniforms, uniforms: this.uniforms,
content: this.content,
} }
} }
@ -716,6 +770,7 @@ function generate_css(tokens, grid_size, seed_value, max_grid, seed_random) {
grid: { x: 1, y: 1, z: 1, count: 1 }, grid: { x: 1, y: 1, z: 1, count: 1 },
random, rand, pick, shuffle, random, rand, pick, shuffle,
max_grid, update_random, max_grid, update_random,
seed_value,
}); });
let { grid, seed } = rules.output(); let { grid, seed } = rules.output();

View File

@ -7,7 +7,10 @@ function generate(token, last) {
} else { } else {
result += token.name + (isInline ? ' ' : '{'); result += token.name + (isInline ? ' ' : '{');
} }
if (Array.isArray(token.value) && token.value.length) { if (token.name === 'style') {
result += token.value;
}
else if (Array.isArray(token.value) && token.value.length) {
let lastGroup = ''; let lastGroup = '';
for (let t of token.value) { for (let t of token.value) {
result += generate(t, lastGroup);; result += generate(t, lastGroup);;

View File

@ -49,11 +49,12 @@ class Tag {
} }
toString() { toString() {
if (this.isTextNode()) { if (this.isTextNode()) {
return this.body; return removeQuotes(this.body);
} }
let attrs = ['']; let attrs = [''];
let body = []; let body = [];
for (let [name, value] of Object.entries(this.attrs)) { for (let [name, value] of Object.entries(this.attrs)) {
value = removeQuotes(value);
attrs.push(`${name}="${value}"`); attrs.push(`${name}="${value}"`);
} }
for (let tag of this.body) { for (let tag of this.body) {
@ -67,11 +68,27 @@ function composeStyleRule(name, value) {
return `${name}:${value};` return `${name}:${value};`
} }
function composeStyle(block) { function removeQuotes(text) {
const style = block.value text = String(text);
.map(n => (n.type === 'block') ? composeStyle(n) : composeStyleRule(n.name, n.value)) let double = text.startsWith('"') && text.endsWith('"');
.join(''); let single = text.startsWith("'") && text.endsWith("'");
return `${block.name}{${style}}`; if (double || single) {
return text.substring(1, text.length - 1);
}
return text;
}
function transformViewBox(token) {
let viewBox = token.detail.value;
let p = token.detail.padding || token.detail.expand;
if (!viewBox.length) {
return '';
}
let [x, y, w, h] = viewBox;
if (p) {
[x, y, w, h] = [x-p, x-p, w+p*2, h+p*2];
}
return `${x} ${y} ${w} ${h}`;
} }
function generate(token, element, parent, root) { function generate(token, element, parent, root) {
@ -82,17 +99,9 @@ function generate(token, element, parent, root) {
if (token.type === 'block') { if (token.type === 'block') {
// style tag // style tag
if (token.name === 'style') { if (token.name === 'style') {
if (Array.isArray(token.value)) { let el = new Tag('style');
let el = new Tag('style'); el.append(token.value);
let styles = []; element.append(el);
for (let block of token.value) {
if (block.type === 'block') {
styles.push(composeStyle(block));
}
}
el.append(styles.join(''));
element.append(el);
}
} }
// normal svg elements // normal svg elements
else { else {
@ -123,9 +132,8 @@ function generate(token, element, parent, root) {
} }
} }
} }
if (token.type === 'statement') { if (token.type === 'statement' && !token.variable) {
let isTextNode = parent && /^(text|tspan|textPath|title|desc)$/i.test(parent.name); if (token.name === 'content') {
if (isTextNode && token.name === 'content') {
let text = new Tag('text-node', token.value); let text = new Tag('text-node', token.value);
element.append(text); element.append(text);
} }
@ -147,7 +155,14 @@ function generate(token, element, parent, root) {
value = `#${id}`; value = `#${id}`;
} }
} }
element.attr(token.name, value); if (/viewBox/i.test(token.name)) {
value = transformViewBox(token, value);
if (value) {
element.attr(token.name, value);
}
} else {
element.attr(token.name, value);
}
if (token.name.includes('xlink:')) { if (token.name.includes('xlink:')) {
root.attr('xmlns:xlink', NSXLink); root.attr('xmlns:xlink', NSXLink);
} }

View File

@ -58,44 +58,42 @@ if (typeof customElements !== 'undefined') {
update(styles) { update(styles) {
this.cleanup(); this.cleanup();
// Use old rules to update
let use = this.get_use(); if (!styles) {
if (!styles) styles = un_entity(this.innerHTML); styles = un_entity(this.innerHTML);
this.innerHTML = styles; }
if (this.innerHTML !== styles) {
this.innerHTML = styles;
}
if (!this.grid_size) { if (!this.grid_size) {
this.grid_size = this.get_grid(); this.grid_size = this.get_grid();
} }
let { x: gx, y: gy, z: gz } = this.grid_size; const { x: gx, y: gy, z: gz } = this.grid_size;
const use = this.get_use();
const compiled = this.generate( let old_content = '';
parse_css(use + styles, this.extra) if (this.compiled) {
old_content = this.compiled.content;
}
const compiled = this.generate(parse_css(use + styles, this.extra));
let grid = compiled.grid || this.get_grid();
let { x, y, z } = grid;
let should_rebuild = (
!this.shadowRoot.innerHTML
|| (gx !== x || gy !== y || gz !== z)
|| (JSON.stringify(old_content) !== JSON.stringify(compiled.content))
); );
if (!this.shadowRoot.innerHTML) { Object.assign(this.grid_size, grid);
Object.assign(this.grid_size, compiled.grid);
return this.build_grid(compiled, compiled.grid);
}
if (compiled.grid) { if (should_rebuild) {
let { x, y, z } = compiled.grid; return compiled.grid
if (gx !== x || gy !== y || gz !== z) { ? this.build_grid(compiled, grid)
Object.assign(this.grid_size, compiled.grid); : this.build_grid(this.generate(parse_css(use + styles, this.extra)), grid);
return this.build_grid(compiled, compiled.grid);
}
Object.assign(this.grid_size, compiled.grid);
}
else {
let grid = this.get_grid();
let { x, y, z } = grid;
if (gx !== x || gy !== y || gz !== z) {
Object.assign(this.grid_size, grid);
return this.build_grid(
this.generate(parse_css(use + styles, this.extra)),
grid
);
}
} }
let replace = this.replace(compiled); let replace = this.replace(compiled);
@ -196,7 +194,7 @@ if (typeof customElements !== 'undefined') {
const { keyframes, host, container, cells } = compiled.styles; const { keyframes, host, container, cells } = compiled.styles;
let replace = this.replace(compiled); let replace = this.replace(compiled);
let grid_container = create_grid(grid); let grid_container = create_grid(grid, compiled.content);
let size = (options && options.width && options.height) let size = (options && options.width && options.height)
? `width="${ options.width }" height="${ options.height }"` ? `width="${ options.width }" height="${ options.height }"`
@ -391,7 +389,7 @@ if (typeof customElements !== 'undefined') {
let style_container = get_grid_styles(grid) + host + container; let style_container = get_grid_styles(grid) + host + container;
let style_cells = has_delay ? '' : cells; let style_cells = has_delay ? '' : cells;
const { uniforms } = compiled; const { uniforms, content } = compiled;
let replace = this.replace(compiled); let replace = this.replace(compiled);
@ -401,7 +399,7 @@ if (typeof customElements !== 'undefined') {
<style class="style-container">${ style_container }</style> <style class="style-container">${ style_container }</style>
<style class="style-cells">${ style_cells }</style> <style class="style-cells">${ style_cells }</style>
<svg id="defs" xmlns="http://www.w3.org/2000/svg" style="width:0;height:0"></svg> <svg id="defs" xmlns="http://www.w3.org/2000/svg" style="width:0;height:0"></svg>
${ create_grid(grid) } ${ create_grid(grid, content) }
`; `;
this.set_content('.style-container', replace(style_container)); this.set_content('.style-container', replace(style_container));
@ -588,13 +586,14 @@ function get_basic_styles() {
return ` return `
*, *::after, *::before { *, *::after, *::before {
box-sizing: border-box; box-sizing: border-box;
animation-play-state: var(--cssd-animation-play-state) !important; animation-play-state: var(--cssd-animation-play-state) !important
} }
:host, .host { :host, .host {
display: block; display: block;
visibility: visible; visibility: visible;
width: auto; width: auto;
height: auto; height: auto;
contain: content;
--${ uniform_time.name }: 0 --${ uniform_time.name }: 0
} }
:host([hidden]), .host[hidden] { :host([hidden]), .host[hidden] {
@ -607,20 +606,19 @@ function get_basic_styles() {
display: grid; display: grid;
${ inherited_grid_props } ${ inherited_grid_props }
} }
cell:empty { cell {
position: relative; position: relative;
line-height: 1;
display: grid; display: grid;
place-items: center place-items: center
} }
svg { svg {
position: absolute; position: absolute;
width: 100%; width: 100%;
height: 100%; height: 100%
} }
:host([cssd-paused-animation]) { :host([cssd-paused-animation]) {
--cssd-animation-play-state: paused; --cssd-animation-play-state: paused;
animation-play-state: paused !important; animation-play-state: paused !important
} }
`; `;
} }
@ -635,41 +633,44 @@ function get_grid_styles(grid_obj) {
`; `;
} }
function create_cell(x, y, z) { function get_content(input) {
let cell = document.createElement('cell'); return is_nil(input) ? '' : input;
cell.id = cell_id(x, y, z);
return cell;
} }
function create_grid(grid_obj) { function create_cell(x, y, z, content, child = '') {
let id = cell_id(x, y, z);
let head = get_content(content['#' + id]);
let tail = get_content(child);
return `<cell id="${id}">${head}${tail}</cell>`;
}
function create_grid(grid_obj, content) {
let { x, y, z } = grid_obj || {}; let { x, y, z } = grid_obj || {};
let grid = document.createElement('grid'); let result = '';
let root = document.createDocumentFragment();
if (z == 1) { if (z == 1) {
for (let j = 1; j <= y; ++j) { for (let j = 1; j <= y; ++j) {
for (let i = 1; i <= x; ++i) { for (let i = 1; i <= x; ++i) {
root.appendChild(create_cell(i, j, 1)); result += create_cell(i, j, 1, content);
} }
} }
} }
else { else {
let temp = null; let child = '';
for (let i = 1; i <= z; ++i) { for (let i = z; i >= 1; i--) {
let cell = create_cell(1, 1, i); let cell = create_cell(1, 1, i, content, child);
(temp || root).appendChild(cell); child = cell;
temp = cell;
} }
temp = null; result = child;
} }
grid.className = 'container'; return `<grid class="container">${result}</grid>`;
grid.appendChild(root);
return grid.outerHTML;
} }
export default make_tag_function(rules => { export default make_tag_function(rules => {
let doodle = document.createElement('css-doodle'); if (typeof document !== 'undefined') {
if (doodle.update) { let doodle = document.createElement('css-doodle');
doodle.update(rules); if (doodle.update) {
doodle.update(rules);
}
return doodle;
} }
return doodle;
}); });

View File

@ -265,9 +265,14 @@ function read_arguments(it, composition, doodle) {
let raw = ''; let raw = '';
while (!it.end()) { while (!it.end()) {
c = it.curr(); c = it.curr();
let prev = it.curr(-1);
let next = it.curr(1);
let start = it.index(); let start = it.index();
if ((/[\('"`]/.test(c) && it.curr(-1) !== '\\')) { if ((/[\('"`]/.test(c) && prev !== '\\')) {
if (stack.length) { if (stack.length) {
if ((c !== '(') && last(stack) === '(') {
stack.pop();
}
if (c != '(' && c === last(stack)) { if (c != '(' && c === last(stack)) {
stack.pop(); stack.pop();
} else { } else {
@ -278,7 +283,7 @@ function read_arguments(it, composition, doodle) {
} }
arg += c; arg += c;
} }
else if (c == '@' && !doodle) { else if ((c == '@' || (prev === '.' && composition)) && !doodle) {
if (!group.length) { if (!group.length) {
arg = arg.trimLeft(); arg = arg.trimLeft();
} }
@ -290,7 +295,7 @@ function read_arguments(it, composition, doodle) {
} }
else if (doodle && /[)]/.test(c) || (!doodle && /[,)]/.test(c))) { else if (doodle && /[)]/.test(c) || (!doodle && /[,)]/.test(c))) {
if (stack.length) { if (stack.length) {
if (c == ')') { if (c == ')' && last(stack) === '(') {
stack.pop(); stack.pop();
} }
arg += c; arg += c;
@ -324,7 +329,7 @@ function read_arguments(it, composition, doodle) {
} }
arg += c; arg += c;
} }
if (composition && (it.curr(1) == ')' || !/[0-9a-zA-Z_\-.]/.test(it.curr())) && !stack.length) { if (composition && ((next == ')' || next == ';') || !/[0-9a-zA-Z_\-.]/.test(it.curr())) && !stack.length) {
if (group.length) { if (group.length) {
args.push(normalize_argument(group)); args.push(normalize_argument(group));
} }
@ -377,14 +382,14 @@ function normalize_argument(group) {
function seperate_func_name(name) { function seperate_func_name(name) {
let fname = '', extra = ''; let fname = '', extra = '';
if ((/\D$/.test(name) && !/\d+x\d+/.test(name)) || Math[name.substr(1)]) { if ((/\D$/.test(name) && !/\d+[x-]\d+/.test(name)) || Math[name.substr(1)]) {
return { fname: name, extra } return { fname: name, extra }
} }
for (let i = name.length - 1; i >= 0; i--) { for (let i = name.length - 1; i >= 0; i--) {
let c = name[i]; let c = name[i];
let prev = name[i - 1]; let prev = name[i - 1];
let next = name[i + 1]; let next = name[i + 1];
if (/[\d.]/.test(c) || ((c == 'x') && /\d/.test(prev) && /\d/.test(next))) { if (/[\d.]/.test(c) || ((c == 'x' || c == '-') && /\d/.test(prev) && /\d/.test(next))) {
extra = c + extra; extra = c + extra;
} else { } else {
fname = name.substring(0, i + 1); fname = name.substring(0, i + 1);
@ -399,21 +404,28 @@ function has_times_syntax(token) {
return str.includes('pureName') && str.includes('times'); return str.includes('pureName') && str.includes('times');
} }
function is_svg(name) {
return /^@svg$/i.test(name);
}
function read_func(it) { function read_func(it) {
let func = Tokens.func(); let func = Tokens.func();
let name = '@', c; let name = it.curr(), c;
let has_argument = false; let has_argument = false;
it.next(); if (name === '@') {
it.next();
} else {
name = '@';
}
while (!it.end()) { while (!it.end()) {
c = it.curr(); c = it.curr();
let composition = (c == '.' && it.curr(1) == '@');
let next = it.curr(1); let next = it.curr(1);
let composition = (c == '.' && (next == '@' || /[a-zA-Z]/.test(next)));
if (c == '(' || composition) { if (c == '(' || composition) {
has_argument = true; has_argument = true;
it.next(); it.next();
let [args, raw_args] = read_arguments(it, composition, composible(name)); let [args, raw_args] = read_arguments(it, composition, composible(name));
if (name === '@svg' && /\d\s*{/.test(raw_args)) { if (is_svg(name) && /\d\s*{/.test(raw_args)) {
let parsed_svg = parse_svg(raw_args); let parsed_svg = parse_svg(raw_args);
if (has_times_syntax(parsed_svg)) { if (has_times_syntax(parsed_svg)) {
let svg = generate_svg_extended(parsed_svg); let svg = generate_svg_extended(parsed_svg);
@ -425,16 +437,14 @@ function read_func(it) {
} }
func.arguments = args; func.arguments = args;
break; break;
} else if (!has_argument && next !== '(' && !/[0-9a-zA-Z_\-.]/.test(next)) { } else if (/[0-9a-zA-Z_\-.]/.test(c)) {
name += c; name += c;
break;
} }
else { if (!has_argument && next !== '(' && !/[0-9a-zA-Z_\-.]/.test(next)) {
name += c; break;
} }
it.next(); it.next();
} }
let { fname, extra } = seperate_func_name(name); let { fname, extra } = seperate_func_name(name);
func.name = fname; func.name = fname;
@ -451,8 +461,9 @@ function read_func(it) {
function read_value(it) { function read_value(it) {
let text = Tokens.text(), idx = 0, skip = true, c; let text = Tokens.text(), idx = 0, skip = true, c;
const value = [], stack = []; const value = [];
value[idx] = []; value[idx] = [];
let stack = [], quote_stack = [];
while (!it.end()) { while (!it.end()) {
c = it.curr(); c = it.curr();
@ -475,20 +486,29 @@ function read_value(it) {
value[++idx] = []; value[++idx] = [];
skip = true; skip = true;
} }
else if (/[;}<]/.test(c)) { else if (/[;}<]/.test(c) && !quote_stack.length) {
if (text.value.length) { if (text.value.length) {
value[idx].push(text); value[idx].push(text);
text = Tokens.text(); text = Tokens.text();
} }
break; break;
} }
else if (c == '@') { else if (c == '@' && /\w/.test(it.curr(1))) {
if (text.value.length) { if (text.value.length) {
value[idx].push(text); value[idx].push(text);
text = Tokens.text(); text = Tokens.text();
} }
value[idx].push(read_func(it)); value[idx].push(read_func(it));
} }
else if (c === '"' || c === "'") {
let quote = last(quote_stack);
if (c === quote) {
quote_stack.pop();
} else if (!quote_stack.length) {
quote_stack.push(c);
}
text.value += c;
}
else if (!is.white_space(c) || !is.white_space(it.curr(-1))) { else if (!is.white_space(c) || !is.white_space(it.curr(-1))) {
if (c == '(') stack.push(c); if (c == '(') stack.push(c);
if (c == ')') stack.pop(); if (c == ')') stack.pop();
@ -496,9 +516,11 @@ function read_value(it) {
if (symbols[c] && !/[0-9]/.test(it.curr(-1))) { if (symbols[c] && !/[0-9]/.test(it.curr(-1))) {
c = symbols[c]; c = symbols[c];
} }
text.value += c; text.value += c;
} }
if (it.curr() === ';' || it.curr() == '}') {
break;
}
it.next(); it.next();
} }
if (text.value.length) { if (text.value.length) {

View File

@ -8,19 +8,19 @@ function readStatement(iter, token) {
let stackParen = []; let stackParen = [];
while (iter.next()) { while (iter.next()) {
let { curr, next } = iter.get(); let { curr, next } = iter.get();
let isStatementBreak = !stackQuote.length && !stackParen.length && (!next || curr.isSymbol(';') || next.isSymbol('}')); if (curr.isSymbol('(') && !stackQuote.length) {
if (curr.isSymbol('(')) {
stackParen.push(curr); stackParen.push(curr);
} else if (curr.isSymbol(')')) { } else if (curr.isSymbol(')') && !stackQuote.length) {
stackParen.pop(); stackParen.pop();
} }
let isStatementBreak = !stackQuote.length && !stackParen.length && (!next || curr.isSymbol(';') || next.isSymbol('}'));
if (curr.isSymbol("'", '"')) { if (curr.isSymbol("'", '"')) {
if (curr.status === 'open') { if (curr.status === 'open') {
stackQuote.push(curr); stackQuote.push(curr);
} else { } else {
stackQuote.pop(); stackQuote.pop();
} }
if (next.isSymbol('}') && !stackQuote.length) { if ((next && next.isSymbol('}')) && !stackQuote.length) {
isStatementBreak = true; isStatementBreak = true;
} }
} }
@ -47,18 +47,13 @@ function readStatement(iter, token) {
} }
break; break;
} }
// skip quotes fragment.push(curr);
let skip = (curr.status == 'open' && stackQuote.length == 1)
|| (curr.status == 'close' && !stackQuote.length);
if (!skip) {
fragment.push(curr);
}
if (isStatementBreak) { if (isStatementBreak) {
break; break;
} }
} }
if (fragment.length && !inlineBlock) { if (fragment.length && !inlineBlock) {
token._valueTokens = fragment;
token.value = joinToken(fragment); token.value = joinToken(fragment);
} else if (inlineBlock) { } else if (inlineBlock) {
token.value = inlineBlock; token.value = inlineBlock;
@ -66,7 +61,26 @@ function readStatement(iter, token) {
if (token.origin) { if (token.origin) {
token.origin.value = token.value; token.origin.value = token.value;
} }
return token return token;
}
function readStyle(iter) {
let stack = [];
let style = [];
while (iter.next()) {
let { curr } = iter.get();
if (curr.isSymbol('{')) {
stack.push(curr.value);
} else if (curr.isSymbol('}')) {
if (stack.length) {
stack.pop();
} else {
break;
}
}
style.push(curr.value);
}
return style.join('');
} }
function walk(iter, parentToken) { function walk(iter, parentToken) {
@ -77,13 +91,13 @@ function walk(iter, parentToken) {
while (iter.next()) { while (iter.next()) {
let { prev, curr, next } = iter.get(); let { prev, curr, next } = iter.get();
let isBlockBreak = !next || curr.isSymbol('}');
if (curr.isSymbol('(')) { if (curr.isSymbol('(')) {
stack.push(curr.value); stack.push(curr.value);
} }
if (curr.isSymbol(')')) { if (curr.isSymbol(')')) {
stack.pop(); stack.pop();
} }
let isBlockBreak = !next || curr.isSymbol('}');
if (isBlock(tokenType) && isBlockBreak) { if (isBlock(tokenType) && isBlockBreak) {
if (!next && rules.length && !curr.isSymbol('}')) { if (!next && rules.length && !curr.isSymbol('}')) {
let last = rules[rules.length - 1].value; let last = rules[rules.length - 1].value;
@ -104,19 +118,29 @@ function walk(iter, parentToken) {
} }
let tokenName = selectors.pop(); let tokenName = selectors.pop();
let skip = isSkip(...selectors, parentToken.name, tokenName); let skip = isSkip(...selectors, parentToken.name, tokenName);
let block = resolveId(walk(iter, splitTimes(tokenName, {
type: 'block', if (tokenName === 'style') {
name: tokenName, rules.push({
value: []
})), skip);
while (tokenName = selectors.pop()) {
block = resolveId(splitTimes(tokenName, {
type: 'block', type: 'block',
name: tokenName, name: tokenName,
value: [block] value: readStyle(iter)
}), skip); });
} else {
let block = resolveId(walk(iter, splitTimes(tokenName, {
type: 'block',
name: tokenName,
value: []
})), skip);
while (tokenName = selectors.pop()) {
block = resolveId(splitTimes(tokenName, {
type: 'block',
name: tokenName,
value: [block]
}), skip);
}
rules.push(block);
} }
rules.push(block);
fragment = []; fragment = [];
} }
else if ( else if (
@ -137,15 +161,21 @@ function walk(iter, parentToken) {
}; };
} }
let statement = readStatement(iter, intial); let statement = readStatement(iter, intial);
let groupdValue = parseValueGroup(statement.value); let groupdValue = parseValueGroup(statement.value);
let expand = (props.length > 1 && groupdValue.length === props.length); let expand = (props.length > 1 && groupdValue.length === props.length);
props.forEach((prop, i) => { props.forEach((prop, i) => {
let item = Object.assign({}, statement, { name: prop }); let item = Object.assign({}, statement, { name: prop });
if (/^\-\-/.test(prop)) {
item.variable = true;
}
if (expand) { if (expand) {
item.value = groupdValue[i]; item.value = groupdValue[i];
} }
if (/viewBox/i.test(prop)) {
item.detail = parseViewBox(item.value, item._valueTokens);
}
delete item._valueTokens;
rules.push(item); rules.push(item);
}); });
if (isBlock(tokenType)) { if (isBlock(tokenType)) {
@ -250,6 +280,30 @@ function getSelectors(tokens) {
return result; return result;
} }
function parseViewBox(value, tokens) {
const viewBox = { value: [] };
let temp;
if (!Array.isArray(tokens)) {
return viewBox;
}
for (let token of tokens) {
if (token.isSpace() || token.isSymbol(',', ';')) {
continue;
}
if (viewBox.value.length < 4 && token.isNumber()) {
viewBox.value.push(Number(token.value));
}
else if (token.isNumber() && temp) {
viewBox[temp] = Number(token.value);
temp = null;
}
else if (token.isWord()) {
temp = token.value;
}
}
return viewBox;
}
function splitTimes(name, object) { function splitTimes(name, object) {
let target = Object.assign({}, object); let target = Object.assign({}, object);
if (/\*\s*[0-9]/.test(name)) { if (/\*\s*[0-9]/.test(name)) {
@ -271,12 +325,20 @@ function isBlock(type) {
} }
function skipHeadSVG(block) { function skipHeadSVG(block) {
let head = block && block.value && block.value[0]; let headSVG, headVariables = [];
if (head && head.name === 'svg' && isBlock(head.type)) { for (let item of block.value) {
return skipHeadSVG(head); if (item.name === 'svg') {
} else { headSVG = item;
return block; }
if (item.variable) {
headVariables.push(item);
}
} }
if (headSVG) {
headSVG.value.push(...headVariables);
return headSVG;
}
return block;
} }
function parse(source, root) { function parse(source, root) {

View File

@ -1,12 +1,13 @@
import { is_empty } from '../utils/index.js'; import { is_empty } from '../utils/index.js';
import { scan, iterator } from './tokenizer.js'; import { scan, iterator } from './tokenizer.js';
function parse(input, option = {symbol: ',', noSpace: false}) { function parse(input, option = {symbol: ',', noSpace: false, verbose: false }) {
let group = []; let group = [];
let skip = false; let skip = false;
let tokens = []; let tokens = [];
let parenStack = []; let parenStack = [];
let quoteStack = []; let quoteStack = [];
let lastGroupName = '';
if (is_empty(input)) { if (is_empty(input)) {
return group; return group;
@ -15,11 +16,25 @@ function parse(input, option = {symbol: ',', noSpace: false}) {
let iter = iterator(scan(input)); let iter = iterator(scan(input));
function isSeperator(token) { function isSeperator(token) {
let symbol = option.symbol || ','; let symbol = option.symbol || [','];
if (option.noSpace) { if (!Array.isArray(symbol)) {
return token.isSymbol(symbol); symbol = [symbol];
}
if (option.noSpace) {
return token.isSymbol(...symbol);
}
return token.isSymbol(...symbol) || token.isSpace();
}
function addGroup(tokens) {
let value = joinTokens(tokens);
if (option.verbose) {
if (lastGroupName.length || value.length) {
group.push({ group: lastGroupName, value });
}
} else {
group.push(value);
} }
return token.isSymbol(symbol) || token.isSpace();
} }
while (iter.next()) { while (iter.next()) {
@ -43,7 +58,9 @@ function parse(input, option = {symbol: ',', noSpace: false}) {
if (isNextSpace || isPrevSpace) continue; if (isNextSpace || isPrevSpace) continue;
} }
if (emptyStack && isSeperator(curr)) { if (emptyStack && isSeperator(curr)) {
group.push(joinTokens(tokens)); let groupName = lastGroupName;
addGroup(tokens);
lastGroupName = curr.value;
tokens = []; tokens = [];
} else { } else {
tokens.push(curr); tokens.push(curr);
@ -51,7 +68,7 @@ function parse(input, option = {symbol: ',', noSpace: false}) {
} }
if (tokens.length) { if (tokens.length) {
group.push(joinTokens(tokens)); addGroup(tokens);
} }
return group; return group;

View File

@ -7,6 +7,7 @@ const symbols = [
':', ';', ',', '(', ')', '[', ']', ':', ';', ',', '(', ')', '[', ']',
'{', '}', 'π', '±', '+', '-', '*', '{', '}', 'π', '±', '+', '-', '*',
'/', '%', '"', "'", '`', '@', '=', '/', '%', '"', "'", '`', '@', '=',
'^',
]; ];
const is = { const is = {

View File

@ -23,7 +23,7 @@ const map_top_bottom = {
export default add_alias({ export default add_alias({
size(value, { is_special_selector, grid }) { size(value, { is_special_selector, grid }) {
let [w, h = w] = parse_value_group(value); let [w, h = w, ratio] = parse_value_group(value);
if (is_preset(w)) { if (is_preset(w)) {
[w, h] = get_preset(w, h); [w, h] = get_preset(w, h);
} }
@ -31,11 +31,22 @@ export default add_alias({
width: ${ w }; width: ${ w };
height: ${ h }; height: ${ h };
`; `;
if (is_special_selector) { if (w === 'auto' || h === 'auto') {
if (w === 'auto' || h === 'auto') { if (ratio) {
styles += `aspect-ratio: ${ grid.ratio };`; if (/^\(.+\)$/.test(ratio)) {
ratio = ratio.substring(1, ratio.length - 1);
} else if (!/^calc/.test(ratio)) {
ratio = `calc(${ratio})`;
}
if (!is_special_selector) {
styles += `aspect-ratio: ${ ratio };`;
}
} }
} else { if (is_special_selector) {
styles += `aspect-ratio: ${ ratio || grid.ratio };`;
}
}
if (!is_special_selector) {
styles += ` styles += `
--internal-cell-width: ${ w }; --internal-cell-width: ${ w };
--internal-cell-height: ${ h }; --internal-cell-height: ${ h };
@ -44,7 +55,7 @@ export default add_alias({
return styles; return styles;
}, },
position(value, { extra }) { place(value, { extra }) {
let [left, top = '50%'] = parse_value_group(value); let [left, top = '50%'] = parse_value_group(value);
left = map_left_right[left] || left; left = map_left_right[left] || left;
top = map_top_bottom[top] || top; top = map_top_bottom[top] || top;
@ -60,16 +71,45 @@ export default add_alias({
margin-top: calc(${ ch } / -2); margin-top: calc(${ ch } / -2);
grid-area: unset; grid-area: unset;
--plot-angle: ${ extra || 0 }; --plot-angle: ${ extra || 0 };
transform: rotate(${ extra || 0 }deg); rotate: ${ extra || 0 }deg;
`; `;
}, },
grid(value, options) { grid(value, options) {
let [grid, size] = parse_value_group(value, { symbol: '/', noSpace: true }); let result = {
return { clip: true,
grid: parse_grid(grid, options.max_grid),
size: size ? this.size(size, options) : ''
}; };
if (/no\-*clip/i.test(value)) {
result.clip = false;
value = value.replace(/no\-*clip/i, '');
}
let groups = parse_value_group(value, {
symbol: ['/', '+', '*', '|', '-'],
noSpace: true,
verbose: true
});
for (let { group, value } of groups) {
if (group === '+') result.scale = value;
if (group === '*') result.rotate = value;
if (group === '/') {
if (result.size === undefined) result.size = this.size(value, options);
else result.fill = value;
}
if ((group === '|' || group == '-' || group == '') && !result.grid) {
result.grid = parse_grid(value, options.max_grid);
if (group === '|') {
result.flexColumn = true;
}
if (group === '-') {
result.flexRow = true;
}
}
}
return result;
},
gap(value) {
return value;
}, },
seed(value) { seed(value) {
@ -91,11 +131,13 @@ export default add_alias({
} }
}, },
content(value) {
return value;
},
}, { }, {
'place': 'position',
// legacy names. // legacy names.
'place-cell': 'position', 'place-cell': 'place',
'offset': 'position', 'offset': 'place',
'position': 'place',
}); });

View File

@ -19,7 +19,7 @@ function range(start, stop, step) {
|| (step < 0 && start > stop)) { || (step < 0 && start > stop)) {
range.push(start); range.push(start);
start += step; start += step;
if (count++ >= 1000) break; if (count++ >= 65535) break;
} }
if (!range.length) range.push(old); if (!range.length) range.push(old);
return range; return range;
@ -57,17 +57,43 @@ function lazy(fn) {
} }
function sequence(count, fn) { function sequence(count, fn) {
let [x, y = 1] = String(count).split('x'); let [x, y = 1] = String(count).split(/[x-]/);
x = clamp(Math.ceil(x) || 1, 1, 65536); let [cx, cy] = [Math.ceil(x), Math.ceil(y)];
y = clamp(Math.ceil(y) || 1, 1, 65536); if (is_invalid_number(cx)) cx = 1;
if (is_invalid_number(cy)) cy = 1;
x = clamp(cx, 0, 65536);
y = clamp(cy, 0, 65536);
let max = x * y; let max = x * y;
let ret = []; let ret = [];
let index = 1; let index = 1;
for (let i = 1; i <= y; ++i) {
for (let j = 1; j <= x; ++j) { if (/x/.test(count)) {
ret.push(fn(index++, j, i, max, x, y)); for (let i = 1; i <= y; ++i) {
for (let j = 1; j <= x; ++j) {
ret.push(fn(index++, j, i, max, x, y, index));
}
} }
} }
else if (/-/.test(count)) {
max = Math.abs(x - y) + 1;
if (x <= y) {
for (let i = x; i <= y; ++i) {
ret.push(fn(i, i, 1, max, max, 1, index++));
}
} else {
for (let i = x; i >= y; --i) {
ret.push(fn(i, i, 1, max, max, 1, index++));
}
}
}
else {
for (let i = 1; i <= x; ++i) {
ret.push(fn(i, i, 1, x, x, 1, index++));
}
}
return ret; return ret;
} }

View File

@ -130,6 +130,30 @@ test('multiple content', t => {
); );
}); });
test('remove quotes around text', t => {
compare(t,
`text {
content: "hello";
}`,
trim(`
<svg xmlns="http://www.w3.org/2000/svg">
<text>hello</text>
</svg>
`)
);
compare(t,
`text {
content: 'hello';
}`,
trim(`
<svg xmlns="http://www.w3.org/2000/svg">
<text>hello</text>
</svg>
`)
);
});
test('style tag', t => { test('style tag', t => {
compare(t, compare(t,
`svg { `svg {
@ -138,6 +162,8 @@ test('style tag', t => {
circle { circle {
fill: red; fill: red;
} }
circle:nth-child(1) {
}
} }
}`, }`,
trim(` trim(`
@ -145,6 +171,7 @@ test('style tag', t => {
<circle></circle> <circle></circle>
<style> <style>
circle{fill:red;} circle{fill:red;}
circle:nth-child(1){}
</style> </style>
</svg> </svg>
`) `)
@ -259,3 +286,28 @@ test('do not group elements for empty id', t => {
`) `)
); );
}); });
test('Normalize quoted attribute values', t => {
compare(t,
`circle {
name: "hello";
}
`,
trim(`
<svg xmlns="http://www.w3.org/2000/svg">
<circle name="hello"></circle>
</svg>
`)
);
compare(t,
`circle {
name: 'hello';
}
`,
trim(`
<svg xmlns="http://www.w3.org/2000/svg">
<circle name="hello"></circle>
</svg>
`)
);
});

View File

@ -88,7 +88,6 @@ test('edge cases', t => {
}); });
test('statement', t => { test('statement', t => {
compare(t, 'viewBox: 0 0 0 10', { compare(t, 'viewBox: 0 0 0 10', {
@ -97,7 +96,24 @@ test('statement', t => {
value: [{ value: [{
type: 'statement', type: 'statement',
name: 'viewBox', name: 'viewBox',
value: '0 0 0 10' value: '0 0 0 10',
detail: {
value: [0, 0, 0, 10]
}
}]
});
compare(t, 'viewBox: 0 0 10 10 padding .2', {
name: 'svg',
type: 'block',
value: [{
type: 'statement',
name: 'viewBox',
value: '0 0 10 10 padding .2',
detail: {
value: [0, 0, 10, 10],
padding: .2
}
}] }]
}); });
@ -116,7 +132,6 @@ test('statement', t => {
}); });
test('group property', t => { test('group property', t => {
compare(t, 'cx, cy: 5', { compare(t, 'cx, cy: 5', {
name: 'svg', name: 'svg',
@ -135,10 +150,8 @@ test('group property', t => {
{ type: 'statement', origin: { name: ['cx', 'cy'], value: '5 6' }, name: 'cy', value: '6'}, { type: 'statement', origin: { name: ['cx', 'cy'], value: '5 6' }, name: 'cy', value: '6'},
] ]
}); });
}); });
test('semicolon separated values', t => { test('semicolon separated values', t => {
compare(t, 'values: 60; 100; 180', { compare(t, 'values: 60; 100; 180', {
name: 'svg', name: 'svg',
@ -158,7 +171,6 @@ test('semicolon separated values', t => {
}); });
}); });
test('colon separated properties', t => { test('colon separated properties', t => {
compare(t, 'xlink:href: url(#app)', { compare(t, 'xlink:href: url(#app)', {
name: 'svg', name: 'svg',
@ -177,7 +189,6 @@ test('colon separated properties', t => {
}); });
}); });
test('block names', t => { test('block names', t => {
compare(t, 'g circle { } ', { compare(t, 'g circle { } ', {
name: 'svg', name: 'svg',
@ -230,7 +241,6 @@ test('block names', t => {
} }
] ]
}); });
}); });
test('id expand', t => { test('id expand', t => {
@ -272,7 +282,6 @@ test('id expand', t => {
] ]
}] }]
}); });
}); });
test('empty id expand', t => { test('empty id expand', t => {
@ -312,7 +321,7 @@ test('statement end on quotes', t => {
value: [{ value: [{
name: 'content', name: 'content',
type: 'statement', type: 'statement',
value: '' value: '\'\''
}] }]
}, { }, {
name: 'g', name: 'g',
@ -322,6 +331,24 @@ test('statement end on quotes', t => {
}); });
}); });
test('content values', t => {
compare(t, `
text { content: "world;}" }
`, {
type: 'block',
name: 'svg',
value: [{
type: 'block',
name: 'text',
value: [{
name: 'content',
type: 'statement',
value: '"world;}"'
}]
}]
});
});
test('times syntax', t => { test('times syntax', t => {
compare(t, ` compare(t, `
circle*10 {} circle*10 {}
@ -350,8 +377,6 @@ test('times syntax', t => {
value: [] value: []
}] }]
}); });
}); });
test('complex values with parens', t => { test('complex values with parens', t => {
@ -373,3 +398,27 @@ test('complex values with parens', t => {
}] }]
}); });
}); });
test('svg variable', t => {
compare(t, '--a: 1', {
name: 'svg',
type: 'block',
value: [{
type: 'statement',
name: '--a',
value: '1',
variable: true
}]
});
compare(t, '--a: 1; svg {}', {
name: 'svg',
type: 'block',
value: [{
type: 'statement',
name: '--a',
value: '1',
variable: true
}]
});
});

View File

@ -71,3 +71,29 @@ test('grid value', t => {
}); });
test('space as separator', t => {
compare.use(input => {
return parseValueGroup(input, { symbol: ' ' });
});
compare(t, '5 100%', ['5', '100%']);
compare(t, '5,100%', ['5,100%']);
compare(t, '5, 100% 5', ['5,100%', '5']);
compare(t, '5, 100% 5 8', ['5,100%', '5', '8']);
});
test('verbose option', t => {
compare.use(input => {
return parseValueGroup(input, { symbol: ['v', 'h'], noSpace: true, verbose: true });
});
compare(t, 'v 10 h -10 v 5', [
{ group: 'v', value: '10' },
{ group: 'h', value: '-10' },
{ group: 'v', value: '5' },
]);
});