Skip to content

Commit

Permalink
Drawing Basic Texts
Browse files Browse the repository at this point in the history
  • Loading branch information
luxluth committed Jul 10, 2023
1 parent dfbff92 commit c4d8aac
Show file tree
Hide file tree
Showing 5 changed files with 269 additions and 15 deletions.
10 changes: 3 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,11 @@ export default class ASS {
init() {
if (typeof this.video == 'string') {
this.videoElement = document.querySelector(this.video)
if (this.videoElement === null) {
throw new Error("Unable to find the video element")
}
} else {
this.videoElement = this.video
}
if (this.videoElement === null) { throw new Error("Unable to find the video element") }
} else { this.videoElement = this.video }

this.setCanvasSize()
this.renderer = new Renderer(parse(this.assText), this.canvas as HTMLCanvasElement)
this.renderer = new Renderer(parse(this.assText), this.canvas as HTMLCanvasElement, this.videoElement)
this.videoElement?.addEventListener('loadedmetadata', () => {
this.setCanvasSize()
})
Expand Down
228 changes: 224 additions & 4 deletions src/renderer.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
import type { ParsedASS } from "ass-compiler";
import type { ParsedASS, ParsedASSEventText } from "ass-compiler";
import { SingleStyle, FontDescriptor } from "./types";
import { ruleOfThree, convertAegisubToRGBA } from "./utils";

export class Renderer {
parsedAss: ParsedASS
canvas: HTMLCanvasElement
ctx: CanvasRenderingContext2D
constructor(parsedASS: ParsedASS, canvas: HTMLCanvasElement) {
video: HTMLVideoElement
playerResX: number
playerResY: number
styles: SingleStyle[]
constructor(
parsedASS: ParsedASS,
canvas: HTMLCanvasElement,
video: HTMLVideoElement
) {
this.parsedAss = parsedASS
this.playerResX = parseFloat(this.parsedAss.info.PlayResX)
this.playerResY = parseFloat(this.parsedAss.info.PlayResY)
this.styles = parsedASS.styles.style as SingleStyle[]
this.canvas = canvas
this.video = video
this.ctx = canvas.getContext("2d") as CanvasRenderingContext2D
if (this.ctx === null) { throw new Error("Unable to initilize the Canvas 2D context") }
let data = [
Expand All @@ -15,6 +30,211 @@ export class Renderer {
]
console.debug(data)
}
render() {}
redraw() {}

render() {
this.video.addEventListener('timeupdate', () => {
this.diplay(this.video.currentTime)
})
}

redraw() { this.diplay(this.video.currentTime) }

diplay(time: number) {
const overlappingDialoguesEvents = this.parsedAss.events.dialogue.filter(event =>
event.Start <= time && event.End >= time
);

// Clear the canvas
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);

overlappingDialoguesEvents.forEach(event => {
const { Style, Text } = event;
const style = this.getStyle(Style);
if (style === undefined) { return; }
this.showText(Text, style);
});
}

/*
type SingleStyle = {
Name: string;
Fontname: string;
Fontsize: string;
PrimaryColour: string;
SecondaryColour: string;
OutlineColour: string;
BackColour: string;
Bold: string;
Italic: string;
Underline: string;
StrikeOut: string;
ScaleX: string;
ScaleY: string;
Spacing: string;
Angle: string;
BorderStyle: string;
Outline: string;
Shadow: string;
Alignment: string;
MarginL: string;
MarginR: string;
MarginV: string;
Encoding: string;
}
*/

showText(Text: ParsedASSEventText, style: SingleStyle) {
console.debug(style.Name, Text)
let fontDescriptor = this.getFontDescriptor(style) // FontDescriptor
let c1 = convertAegisubToRGBA(style.PrimaryColour) // primary color
let c2 = convertAegisubToRGBA(style.SecondaryColour) // secondary color
let c3 = convertAegisubToRGBA(style.OutlineColour) // outline color
let c4 = convertAegisubToRGBA(style.BackColour) // shadow color
let marginL = ruleOfThree(this.playerResX, this.canvas.width) * parseFloat(style.MarginL) / 100
let marginV = ruleOfThree(this.playerResY, this.canvas.height) * parseFloat(style.MarginV) / 100
let marginR = ruleOfThree(this.playerResX, this.canvas.width) * parseFloat(style.MarginR) / 100
// console.debug(marginL, marginV, marginR)
let text = Text.parsed[0]?.text as string

this.ctx.font = `${fontDescriptor.fontsize}px ${fontDescriptor.bold ? "bold" : ""} ${fontDescriptor.italic ? "italic" : ""} ${fontDescriptor.fontname}`;
console.debug(this.ctx.font)
let textAlign = this.getAlignment(parseInt(style.Alignment)) as CanvasTextAlign;
let textBaseline = this.getBaseLine(parseInt(style.Alignment)) as CanvasTextBaseline;
this.ctx.fillStyle = c1;
this.ctx.strokeStyle = c3;
this.ctx.lineWidth = ruleOfThree(this.playerResX, this.canvas.width) * parseFloat(style.Outline) / 100 * 2;
console.debug(this.ctx.lineWidth, style.Outline)
this.ctx.lineJoin = "round";
this.ctx.lineCap = "round";
this.ctx.miterLimit = 2;
this.ctx.shadowColor = c4;
this.ctx.shadowBlur = ruleOfThree(this.playerResX, this.canvas.width) * parseFloat(style.Shadow) / 100;
this.ctx.shadowOffsetX = ruleOfThree(this.playerResX, this.canvas.width) * parseFloat(style.Shadow) / 100;
this.ctx.shadowOffsetY = ruleOfThree(this.playerResY, this.canvas.height) * parseFloat(style.Shadow) / 100;

this.drawText(text, textAlign, textBaseline, marginL, marginV, marginR);
}

drawText(
text: string,
textAlign: CanvasTextAlign,
textBaseline: CanvasTextBaseline,
marginL: number,
marginV: number,
marginR: number,
) {
let lines = text.split("\\N");
let lineHeights = lines.map(line => this.ctx.measureText(line).actualBoundingBoxAscent + this.ctx.measureText(line).actualBoundingBoxDescent);
let lineHeight = Math.max(...lineHeights);
let totalHeight = lineHeight * lines.length;
let y = 0;
switch (textBaseline) {
case "top":
y = marginV + lineHeight;
if (lines.length === 1) { y -= lineHeight; } else { y -= totalHeight / lines.length; }
break;
case "middle":
y = (this.canvas.height - totalHeight) / 2 + lineHeight;
break;
case "bottom":
y = this.canvas.height - marginV;
if (lines.length === 1) { y -= lineHeight; } else { y -= totalHeight / lines.length; }
break;
default:
y = marginV + lineHeight;
break;
}

lines.forEach(line => {
let lineWidth = this.ctx.measureText(line).width;
let x = 0;
switch (textAlign) {
case "left":
x = marginL;
break;
case "center":
x = (this.canvas.width - lineWidth) / 2;
break;
case "right":
x = this.canvas.width - marginR - lineWidth;
break;
default:
x = marginL;
break;
}
this.ctx.fillText(line, x, y);
this.ctx.strokeText(line, x, y);
y += lineHeight;
})
}

getAlignment(alignment: number) {
// 1 = (bottom) left
// 2 = (bottom) center
// 3 = (bottom) right
// 4 = (middle) left
// 5 = (middle) center
// 6 = (middle) right
// 7 = (top) left
// 8 = (top) center
// 9 = (top) right
switch (alignment) {
case 1:
case 4:
case 7:
return "left";
case 2:
case 5:
case 8:
return "center";
case 3:
case 6:
case 9:
return "right";
default:
return "start";
}
}

getBaseLine(alignment: number) {
// 1 = (bottom) left
// 2 = (bottom) center
// 3 = (bottom) right
// 4 = (middle) left
// 5 = (middle) center
// 6 = (middle) right
// 7 = (top) left
// 8 = (top) center
// 9 = (top) right
switch (alignment) {
case 1:
case 2:
case 3:
return "bottom";
case 4:
case 5:
case 6:
return "middle";
case 7:
case 8:
case 9:
return "top";
default:
return "alphabetic";
}
}

getStyle(styleName: string) {return this.styles.find(style => style.Name === styleName)}

getFontDescriptor(style: SingleStyle): FontDescriptor {
const fontsize = ruleOfThree(this.playerResY, this.canvas.height) * parseFloat(style.Fontsize) / 100;
return {
fontname: style.Fontname,
fontsize: fontsize,
bold: style.Bold === "-1",
italic: style.Italic === "-1",
underline: style.Underline === "-1",
strikeout: style.StrikeOut === "-1",
};
}
}
37 changes: 36 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,42 @@ export type ASSOptions = {
assText: string,
/**
* The video to display the subtile on.
* Can be either an `HTMLVideoElement` or `string` (html query selector)
* Can be either an `HTMLVideoElement` or `string` (html query selector )
*/
video: HTMLVideoElement | string
}

export type SingleStyle = {
Name: string
Fontname: string
Fontsize: string
PrimaryColour: string
SecondaryColour: string
OutlineColour: string
BackColour: string
Bold: string
Italic: string
Underline: string
StrikeOut: string
ScaleX: string
ScaleY: string
Spacing: string
Angle: string
BorderStyle: string
Outline: string
Shadow: string
Alignment: string
MarginL: string
MarginR: string
MarginV: string
Encoding: string
}

export type FontDescriptor = {
fontname: string
fontsize: number
bold: boolean
italic: boolean
underline: boolean
strikeout: boolean
}
6 changes: 4 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ export function convertAegisubToRGBA(aegisubColor: string) {
let red = parseInt(aegisubColor.slice(2, 4), 16)
let green = parseInt(aegisubColor.slice(4, 6), 16)
let blue = parseInt(aegisubColor.slice(6, 8), 16)

return 'rgba(' + red + ',' + green + ',' + blue + ',' + alpha + ')'
let clr = `rgba(${red}, ${green}, ${blue}, ${alpha})`
console.debug(clr, aegisubColor)
return `rgba(${red}, ${green}, ${blue}, ${alpha})`
}

export function ruleOfThree(
Expand All @@ -34,6 +35,7 @@ export function genRandomString(ln: number) {

export function newCanvas(top: number, left: number, width: number, height: number, insertAfter?: HTMLElement, zIndex?: number) {
const canvas = document.createElement('canvas');
canvas.id = "ASSRendererCanvas-" + genRandomString(10);
canvas.style.position = 'absolute';
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"skipLibCheck": true /* Skip type checking all .d.ts files. */,
"noUncheckedIndexedAccess": true,
"noEmit": true,
"lib": ["esnext", "dom"]
"lib": ["esnext", "dom"],
"removeComments": true,
}
}

0 comments on commit c4d8aac

Please sign in to comment.