Skip to content

Commit

Permalink
refactor: use SVG if xbord != ybord or \1a != 00
Browse files Browse the repository at this point in the history
close #46
  • Loading branch information
weizhenye committed Sep 15, 2024
1 parent 91f5ec5 commit 363c170
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 220 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,4 @@ ASS.js uses many Web APIs to render subtitles, some features will be disabled if
| accel in `\t` | [linear()](https://caniuse.com/mdn-css_types_easing-function_linear-function) | 113 | 112 | 17.2 |
| `\q0` | [text-wrap: balance](https://caniuse.com/css-text-wrap-balance) | 114 | 121 | 17.5 |
| BorderStyle=3 with `\bord0` | [@container](https://caniuse.com/mdn-css_at-rules_container_style_queries_for_custom_properties) | 111 | - | 18.0 |
| `\blur` with `\bord0` | [sign()](https://caniuse.com/mdn-css_types_sign) | - | 118 | 15.4 |
| `\blur` with `\bord0` | [round()](https://caniuse.com/mdn-css_types_round) | 125 | 118 | 15.4 |
180 changes: 100 additions & 80 deletions src/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,31 @@
position: absolute;
z-index: 0;
transform: translate(calc(var(--ass-align-h) * -1), calc(var(--ass-align-v) * -1));
span {
display: inline-block;
}
[data-text] {
display: inline-block;
color: var(--ass-fill-color);
font-size: calc(var(--ass-scale) * var(--ass-real-fs) * 1px);
line-height: calc(var(--ass-scale) * var(--ass-tag-fs) * 1px);
letter-spacing: calc(var(--ass-scale) * var(--ass-tag-fsp) * 1px);
/* for \bord0\blur4 */
/* when x > 0, round(up, sin(x) * sin(x)) === sign(x) */
/* sign() requires higher browser version */
filter: blur(calc(
var(--ass-scale-stroke)
* var(--ass-tag-blur)
* (1 - round(up, sin(var(--ass-tag-xbord)) * sin(var(--ass-tag-xbord))))
* (1 - round(up, sin(var(--ass-tag-ybord)) * sin(var(--ass-tag-ybord))))
* 1px
));
}
[data-is="br"] + [data-is="br"] {
height: calc(var(--ass-scale) * var(--ass-tag-fs) * 1px / 2);
}
}
.ASS-dialogue span {
display: inline-block;
}
.ASS-dialogue [data-text] {
display: inline-block;
color: var(--ass-fill-color);
font-size: calc(var(--ass-scale) * var(--ass-real-fs) * 1px);
line-height: calc(var(--ass-scale) * var(--ass-tag-fs) * 1px);
letter-spacing: calc(var(--ass-scale) * var(--ass-tag-fsp) * 1px);
}
.ASS-dialogue [data-is="br"] + [data-is="br"] {
height: calc(var(--ass-scale) * var(--ass-tag-fs) * 1px / 2);
}

.ASS-dialogue[data-wrap-style="0"],
.ASS-dialogue[data-wrap-style="3"] {
/* \q3 is just treated the same as \q0 in libass */
Expand All @@ -38,65 +49,71 @@
word-break: normal;
white-space: nowrap;
}

.ASS-dialogue [data-border-style="1"] {
position: relative;
/* for \bord0\blur4 */
filter: blur(calc(
var(--ass-tag-blur)
* calc(1 - sign(var(--ass-tag-xbord)))
* calc(1 - sign(var(--ass-tag-ybord)))
* 1px
));
}
.ASS-dialogue [data-border-style="1"]::before,
.ASS-dialogue [data-border-style="1"]::after {
content: attr(data-text);
position: absolute;
top: 0;
left: 0;
z-index: -1;
filter: blur(calc(var(--ass-tag-blur) * 1px));
}
.ASS-dialogue [data-border-style="1"]::before {
color: var(--ass-shadow-color);
transform: translate(
calc(var(--ass-scale-stroke) * var(--ass-tag-xshad) * 1px),
calc(var(--ass-scale-stroke) * var(--ass-tag-yshad) * 1px)
);
-webkit-text-stroke: var(--ass-border-width) var(--ass-shadow-color);
text-shadow: var(--ass-shadow-delta);
opacity: var(--ass-shadow-opacity);
}
.ASS-dialogue [data-border-style="1"]::after {
color: transparent;
-webkit-text-stroke: var(--ass-border-width) var(--ass-border-color);
text-shadow: var(--ass-border-delta);
opacity: var(--ass-border-opacity);
&::before,
&::after {
content: attr(data-text);
position: absolute;
top: 0;
left: 0;
z-index: -1;
filter: blur(calc(var(--ass-scale-stroke) * var(--ass-tag-blur) * 1px));
}
&::before {
color: var(--ass-shadow-color);
-webkit-text-stroke: calc(var(--ass-scale-stroke) * var(--ass-border-width) * 1px) var(--ass-shadow-color);
transform: translate(calc(var(--ass-scale-stroke) * var(--ass-tag-xshad) * 1px),
calc(var(--ass-scale-stroke) * var(--ass-tag-yshad) * 1px));
}
&::after {
color: var(--ass-border-color);
-webkit-text-stroke: calc(var(--ass-scale-stroke) * var(--ass-border-width) * 1px) var(--ass-border-color);
}
&[data-stroke="svg"] {
color: #000;
&::before,
&::after {
opacity: 0;
}
}
}
@container style(--ass-tag-xbord: 0) and style(--ass-tag-ybord: 0) {
.ASS-dialogue [data-border-style="1"]::after {
display: none;
}
}
@container style(--ass-tag-xshad: 0) and style(--ass-tag-yshad: 0) {
.ASS-dialogue [data-border-style="1"]::before {
display: none;
}
}

.ASS-dialogue [data-border-style="3"] {
padding:
calc(var(--ass-scale-stroke) * var(--ass-tag-xbord) * 1px)
calc(var(--ass-scale-stroke) * var(--ass-tag-ybord) * 1px);
position: relative;
filter: blur(calc(var(--ass-tag-blur) * 1px));
}
.ASS-dialogue [data-border-style="3"]::before,
.ASS-dialogue [data-border-style="3"]::after {
content: "";
width: 100%;
height: 100%;
position: absolute;
z-index: -1;
}
.ASS-dialogue [data-border-style="3"]::before {
background-color: var(--ass-shadow-color);
left: calc(var(--ass-scale-stroke) * var(--ass-tag-xshad) * 1px);
top: calc(var(--ass-scale-stroke) * var(--ass-tag-yshad) * 1px);
}
.ASS-dialogue [data-border-style="3"]::after {
background-color: var(--ass-border-color);
left: 0;
top: 0;
filter: blur(calc(var(--ass-scale-stroke) * var(--ass-tag-blur) * 1px));
&::before,
&::after {
content: "";
width: 100%;
height: 100%;
position: absolute;
z-index: -1;
}
&::before {
background-color: var(--ass-shadow-color);
left: calc(var(--ass-scale-stroke) * var(--ass-tag-xshad) * 1px);
top: calc(var(--ass-scale-stroke) * var(--ass-tag-yshad) * 1px);
}
&::after {
background-color: var(--ass-border-color);
left: 0;
top: 0;
}
}
@container style(--ass-tag-xbord: 0) and style(--ass-tag-ybord: 0) {
.ASS-dialogue [data-border-style="3"]::after {
Expand All @@ -108,18 +125,20 @@
background-color: transparent;
}
}

.ASS-dialogue [data-rotate] {
/* TODO: {\an5\fs80\bord0\shad60\frx30\frz30\fry30}1234567890 */
/* TODO: {\frz-90\shad30} */
/* https://github.com/libass/libass/issues/805 */
transform: perspective(312.5px)
rotateY(calc(var(--ass-tag-fry) * 1deg))
rotateX(calc(var(--ass-tag-frx) * 1deg))
rotateZ(calc(var(--ass-tag-frz) * -1deg));
}
.ASS-dialogue [data-text][data-rotate] {
transform-style: preserve-3d;
word-break: normal;
white-space: nowrap;
&[data-text] {
transform-style: preserve-3d;
word-break: normal;
white-space: nowrap;
}
}
.ASS-dialogue [data-scale],
.ASS-dialogue [data-skew] {
Expand All @@ -128,6 +147,7 @@
skew(calc(var(--ass-tag-fax) * 1rad), calc(var(--ass-tag-fay) * 1rad));
transform-origin: var(--ass-align-h) var(--ass-align-v);
}

.ASS-fix-font-size {
font-family: Arial;
line-height: normal;
Expand All @@ -136,9 +156,9 @@
position: absolute;
visibility: hidden;
overflow: hidden;
}
.ASS-fix-font-size span {
position: absolute;
span {
position: absolute;
}
}
.ASS-clip-area {
width: 100%;
Expand All @@ -154,12 +174,12 @@
height: fit-content;
overflow: hidden;
mask-composite: intersect;
}
.ASS-effect-area[data-effect="banner"] {
flex-direction: column;
height: 100%;
}
.ASS-effect-area .ASS-dialogue {
position: static;
transform: none;
&[data-effect="banner"] {
flex-direction: column;
height: 100%;
}
.ASS-dialogue {
position: static;
transform: none;
}
}
71 changes: 43 additions & 28 deletions src/renderer/animation.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,42 @@
import { color2rgba, alpha2opacity, initAnimation } from '../utils.js';
import { color2rgba, initAnimation } from '../utils.js';
import { getRealFontSize } from './font-size.js';
// eslint-disable-next-line import/no-cycle
import { createRectClip } from './clip.js';
import { rotateTags, skewTags, scaleTags } from './transform.js';

const strokeTags = ['blur', 'xbord', 'ybord', 'xshad', 'yshad'];
if (window.CSS.registerProperty) {
[
'real-fs', 'tag-fs', 'tag-fsp', 'border-width',
...[...strokeTags, ...rotateTags, ...skewTags].map((tag) => `tag-${tag}`),
].forEach((k) => {
window.CSS.registerProperty({
name: `--ass-${k}`,
syntax: '<number>',
inherits: true,
initialValue: 0,
});
});
[
'border-opacity', 'shadow-opacity',
...scaleTags.map((tag) => `tag-${tag}`),
].forEach((k) => {
window.CSS.registerProperty({
name: `--ass-${k}`,
syntax: '<number>',
inherits: true,
initialValue: 1,
});
});
['fill-color', 'border-color', 'shadow-color'].forEach((k) => {
window.CSS.registerProperty({
name: `--ass-${k}`,
syntax: '<color>',
inherits: true,
initialValue: 'transparent',
});
});
}

export function createEffect(effect, duration) {
// TODO: when effect and move both exist, its behavior is weird, for now only move works.
Expand Down Expand Up @@ -79,23 +114,6 @@ export function createAnimatableVars(tag) {
.map(([k, v]) => [`--ass-${k}`, v]);
}

if (window.CSS.registerProperty) {
['real-fs', 'tag-fs', 'tag-fsp'].forEach((k) => {
window.CSS.registerProperty({
name: `--ass-${k}`,
syntax: '<number>',
inherits: true,
initialValue: '0',
});
});
window.CSS.registerProperty({
name: '--ass-fill-color',
syntax: '<color>',
inherits: true,
initialValue: 'transparent',
});
}

// use linear() to simulate accel
function getEasing(duration, accel) {
if (accel === 1) return 'linear';
Expand Down Expand Up @@ -125,17 +143,11 @@ function createTagKeyframes(fromTag, tag, key) {
if (key === 'a1' || key === 'c1') {
return [['fill-color', color2rgba((tag.a1 || fromTag.a1) + (tag.c1 || fromTag.c1))]];
}
if (key === 'c3') {
return [['border-color', color2rgba(`00${tag.c3}`)]];
}
if (key === 'a3') {
return [['border-opacity', alpha2opacity(tag.a3)]];
if (key === 'a3' || key === 'c3') {
return [['border-color', color2rgba((tag.a3 || fromTag.a3) + (tag.c3 || fromTag.c3))]];
}
if (key === 'c4') {
return [['shadow-color', color2rgba(`00${tag.c4}`)]];
}
if (key === 'a4') {
return [['shadow-opacity', alpha2opacity(tag.a4)]];
if (key === 'a4' || key === 'c4') {
return [['shadow-color', color2rgba((tag.a4 || fromTag.a4) + (tag.c4 || fromTag.c4))]];
}
if (key === 'fs') {
return [
Expand All @@ -146,6 +158,9 @@ function createTagKeyframes(fromTag, tag, key) {
if (key === 'fscx' || key === 'fscy') {
return [[`tag-${key}`, (value || 100) / 100]];
}
if (key === 'xbord' || key === 'ybord') {
return [['border-width', value * 2]];
}
return [[`tag-${key}`, value]];
}

Expand Down
28 changes: 22 additions & 6 deletions src/renderer/dom.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { createDrawing } from './drawing.js';
import { createAnimatableVars, createDialogueAnimations, createTagAnimations } from './animation.js';
import { createCSSStroke } from './stroke.js';
import { createStrokeVars, createStrokeFilter } from './stroke.js';
import { rotateTags, scaleTags, skewTags, createTransform } from './transform.js';
import { createSVGEl } from '../utils.js';

function encodeText(text, q) {
return text
Expand Down Expand Up @@ -32,11 +33,19 @@ export function createDialogue(dialogue, store) {
const tag = { ...sliceTag, ...fragment.tag };
let cssText = '';
const cssVars = [];
if (!drawing) {
cssVars.push(...createAnimatableVars(tag));
const scale = store.sbas ? store.scale : 1;
cssVars.push(...createCSSStroke(tag, scale));

cssVars.push(...createStrokeVars(tag));
let stroke = null;
const hasStroke = tag.xbord || tag.ybord || tag.xshad || tag.yshad;
if (hasStroke && (drawing || tag.a1 !== '00' || tag.xbord !== tag.ybord)) {
const filter = createStrokeFilter(tag, store.sbas ? store.scale : 1);
const svg = createSVGEl('svg', [['width', 0], ['height', 0]]);
svg.append(filter.el);
stroke = { id: filter.id, el: svg };
}

cssVars.push(...createAnimatableVars(tag));
if (!drawing) {
cssText += `font-family:"${tag.fn}";`;
cssText += tag.b ? `font-weight:${tag.b === 1 ? 'bold' : tag.b};` : '';
cssText += tag.i ? 'font-style:italic;' : '';
Expand Down Expand Up @@ -89,8 +98,15 @@ export function createDialogue(dialogue, store) {
}
const el = hasScale || hasSkew ? $ssspan : $span;
el.dataset.text = content;
if (tag.xbord || tag.ybord || tag.xshad || tag.yshad) {
if (hasStroke) {
el.dataset.borderStyle = borderStyle;
el.dataset.stroke = 'css';
}
if (stroke) {
el.dataset.stroke = 'svg';
// TODO: it doesn't support animation
el.style.filter = `url(#${stroke.id})`;
el.append(stroke.el);
}
}
$span.style.cssText += cssText;
Expand Down
Loading

0 comments on commit 363c170

Please sign in to comment.