diff --git a/docs/index.html b/docs/index.html index a61832d5..986aff5b 100644 --- a/docs/index.html +++ b/docs/index.html @@ -79,6 +79,7 @@

Index

@@ -400,7 +401,22 @@

example

nico:stroke:#f0f nico:stroke:#fff5
-

コメントの縁取りの色を変更することができます

+

コメントの縁取りの色を変更するできます

+

色の指定は色コマンドまたはカラーコードで行ってください

+

カラーコードは透明度も指定可能です

+
+ +

Waku

+
+

format

+
nico:waku:(color|color code)
+

example

+
+
nico:waku:red
+nico:waku:#f0f
+nico:waku:#fff5
+
+

コメント単位で枠を表示するできます

色の指定は色コマンドまたはカラーコードで行ってください

カラーコードは透明度も指定可能です

diff --git a/package.json b/package.json index 43b02da1..8220fb41 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@xpadev-net/niconicomments", - "version": "0.2.45", + "version": "0.2.46", "description": "NiconiComments is a comment drawing library that is somewhat compatible with the official Nico Nico Douga player.", "main": "dist/bundle.js", "types": "dist/bundle.d.ts", diff --git a/src/@types/format.v1.d.ts b/src/@types/format.v1.d.ts index fb681988..2ab170f8 100644 --- a/src/@types/format.v1.d.ts +++ b/src/@types/format.v1.d.ts @@ -2,7 +2,7 @@ export type v1Thread = { id: string; fork: string; commentCount: number; - comments: { [key: string]: v1Comment }; + comments: v1Comment[]; }; type v1Comment = { id: string; diff --git a/src/@types/types.d.ts b/src/@types/types.d.ts index 297226d3..01aebba6 100644 --- a/src/@types/types.d.ts +++ b/src/@types/types.d.ts @@ -16,6 +16,7 @@ type formattedCommentWithFont = { font: commentFont; color: string; strokeColor?: string; + wakuColor?: string; full: boolean; ender: boolean; _live: boolean; @@ -117,6 +118,7 @@ type parsedCommand = { fontSize: number | undefined; color: string | undefined; strokeColor?: string; + wakuColor?: string; font: commentFont | undefined; full: boolean; ender: boolean; diff --git a/src/comments/FlashComment.ts b/src/comments/FlashComment.ts index 6081fa80..58a76f96 100644 --- a/src/comments/FlashComment.ts +++ b/src/comments/FlashComment.ts @@ -1,25 +1,24 @@ import { getConfig, + getFlashFontIndex, + getFlashFontName, getPosX, getStrokeColor, + nativeSort, parseCommandAndNicoScript, parseFont, } from "@/util"; -import typeGuard from "@/typeGuard"; import { config, options } from "@/definition/config"; import { nicoScripts } from "@/contexts/nicoscript"; import { imageCache } from "@/contexts/cache"; import type { IComment } from "@/@types/IComment"; import type { - commentContentIndex, commentContentItem, - commentFlashFont, commentMeasuredContentItem, formattedCommentWithFont, formattedCommentWithSize, measureTextInput, measureTextResult, - parsedCommand, } from "@/@types/types"; import type { formattedComment } from "@/@types/format.formatted"; @@ -75,59 +74,6 @@ class FlashComment implements IComment { return this.comment.lineCount; } - /** - * コメントに含まれるコマンドを解釈する - * @param comment- 独自フォーマットのコメントデータ - * @returns {{loc: string|undefined, size: string|undefined, color: string|undefined, fontSize: number|undefined, ender: boolean, font: string|undefined, full: boolean, _live: boolean, invisible: boolean, long:number|undefined}} - */ - parseCommand(comment: formattedComment): parsedCommand { - const metadata = comment.mail; - const result: parsedCommand = { - loc: undefined, - size: undefined, - fontSize: undefined, - color: undefined, - font: undefined, - full: false, - ender: false, - _live: false, - invisible: false, - long: undefined, - }; - for (let command of metadata) { - command = command.toLowerCase(); - const match = command.match(/^@([0-9.]+)/); - if (match && match[1]) { - result.long = Number(match[1]); - } else if (result.loc === undefined && typeGuard.comment.loc(command)) { - result.loc = command; - } else if (result.size === undefined && typeGuard.comment.size(command)) { - result.size = command; - result.fontSize = getConfig(config.fontSize, true)[command].default; - } else { - if (result.color === undefined) { - const color = config.colors[command]; - if (color) { - result.color = color; - continue; - } else { - const match = command.match(/#[0-9a-z]{3,6}/); - if (match && match[0] && comment.premium) { - result.color = match[0].toUpperCase(); - continue; - } - } - } - if (result.font === undefined && typeGuard.comment.font(command)) { - result.font = command; - } else if (typeGuard.comment.command.key(command)) { - result[command] = true; - } - } - } - return result; - } - /** * コメントに含まれるニコスクリプトを処理する * @param comment @@ -140,18 +86,6 @@ class FlashComment implements IComment { const parts = (comment.content.match(/\n|[^\n]+/g) || []).map((val) => Array.from(val.match(/[ -~。-゚]+|[^ -~。-゚]+/g) || []) ); - const regex = { - simsunStrong: new RegExp(config.flashChar.simsunStrong), - simsunWeak: new RegExp(config.flashChar.simsunWeak), - gulim: new RegExp(config.flashChar.gulim), - gothic: new RegExp(config.flashChar.gothic), - }; - const getFontName = (font: string) => - font.match("^simsun.+") - ? "simsun" - : font === "gothic" - ? "defont" - : (font as commentFlashFont); for (const line of parts) { const lineContent: commentContentItem[] = []; for (const part of line) { @@ -159,34 +93,16 @@ class FlashComment implements IComment { lineContent.push({ content: part }); continue; } - const index: commentContentIndex[] = []; - let match; - if ((match = regex.simsunStrong.exec(part)) !== null) { - index.push({ font: "simsunStrong", index: match.index }); - } - if ((match = regex.simsunWeak.exec(part)) !== null) { - index.push({ font: "simsunWeak", index: match.index }); - } - if ((match = regex.gulim.exec(part)) !== null) { - index.push({ font: "gulim", index: match.index }); - } - if ((match = regex.gothic.exec(part)) !== null) { - index.push({ font: "gothic", index: match.index }); - } + const index = getFlashFontIndex(part); if (index.length === 0) { lineContent.push({ content: part }); } else if (index.length === 1 && index[0]) { - lineContent.push({ content: part, font: getFontName(index[0].font) }); - } else { - index.sort((a, b) => { - if (a.index > b.index) { - return 1; - } else if (a.index < b.index) { - return -1; - } else { - return 0; - } + lineContent.push({ + content: part, + font: getFlashFontName(index[0].font), }); + } else { + index.sort(nativeSort((val) => val.index)); if (config.flashMode === "xp") { let offset = 0; for (let i = 1; i < index.length; i++) { @@ -195,7 +111,7 @@ class FlashComment implements IComment { if (currentVal === undefined || lastVal === undefined) continue; lineContent.push({ content: part.slice(offset, currentVal.index), - font: getFontName(lastVal.font), + font: getFlashFontName(lastVal.font), }); offset = currentVal.index; } @@ -203,7 +119,7 @@ class FlashComment implements IComment { if (val) lineContent.push({ content: part.slice(offset), - font: getFontName(val.font), + font: getFlashFontName(val.font), }); } else { const firstVal = index[0], @@ -215,16 +131,16 @@ class FlashComment implements IComment { if (firstVal.font !== "gothic") { lineContent.push({ content: part, - font: getFontName(firstVal.font), + font: getFlashFontName(firstVal.font), }); } else { lineContent.push({ content: part.slice(0, secondVal.index), - font: getFontName(firstVal.font), + font: getFlashFontName(firstVal.font), }); lineContent.push({ content: part.slice(secondVal.index), - font: getFontName(secondVal.font), + font: getFlashFontName(secondVal.font), }); } } @@ -296,9 +212,7 @@ class FlashComment implements IComment { spacedWidth_arr = []; let currentWidth = 0, spacedWidth = 0; - for (let i = 0; i < comment.content.length; i++) { - const item = comment.content[i]; - if (item === undefined) continue; + for (const item of comment.content) { const lines = item.content.split("\n"); const widths = []; @@ -458,6 +372,15 @@ class FlashComment implements IComment { } this.context.drawImage(this.image, posX, posY); } + if (this.comment.wakuColor) { + this.context.strokeStyle = this.comment.wakuColor; + this.context.strokeRect( + posX, + posY, + this.comment.width, + this.comment.height + ); + } if (showCollision) { this.context.strokeStyle = "rgba(255,0,255,1)"; this.context.strokeRect( @@ -549,9 +472,7 @@ class FlashComment implements IComment { let lastFont = this.comment.font, leftOffset = 0, lineCount = 0; - for (let i = 0; i < this.comment.content.length; i++) { - const item = this.comment.content[i]; - if (!item) continue; + for (const item of this.comment.content) { if (lastFont !== (item.font || this.comment.font)) { lastFont = item.font || this.comment.font; context.font = parseFont(lastFont, this.comment.fontSize); diff --git a/src/comments/HTML5Comment.ts b/src/comments/HTML5Comment.ts index 74350cbf..9a74eb48 100644 --- a/src/comments/HTML5Comment.ts +++ b/src/comments/HTML5Comment.ts @@ -290,6 +290,15 @@ class HTML5Comment implements IComment { } this.context.drawImage(this.image, posX, posY); } + if (this.comment.wakuColor) { + this.context.strokeStyle = this.comment.wakuColor; + this.context.strokeRect( + posX, + posY, + this.comment.width, + this.comment.height + ); + } if (showCollision) { const scale = getConfig(config.commentScale, false); this.context.strokeStyle = "rgba(0,255,255,1)"; @@ -378,9 +387,7 @@ class HTML5Comment implements IComment { const paddingTop = (10 - scale * 10) * (this.comment.lineCount / config.hiResCommentCorrection); - for (let i = 0; i < this.comment.content.length; i++) { - const item = this.comment.content[i]; - if (!item) continue; + for (const item of this.comment.content) { const lines = item.content.split("\n"); for (let j = 0; j < lines.length; j++) { const line = lines[j]; diff --git a/src/inputParser.ts b/src/inputParser.ts index 8a71131e..28fb3d6b 100644 --- a/src/inputParser.ts +++ b/src/inputParser.ts @@ -112,9 +112,8 @@ const fromFormatted = ( const fromLegacy = (data: rawApiResponse[]): formattedComment[] => { const data_: formattedComment[] = [], userList: string[] = []; - for (let i = 0; i < data.length; i++) { - const val = data[i]; - if (!val || !typeGuard.legacy.apiChat(val?.chat)) continue; + for (const val of data) { + if (!typeGuard.legacy.apiChat(val.chat)) continue; const value = val.chat; if (value.deleted !== 1) { const tmpParam: formattedComment = { @@ -233,9 +232,7 @@ const fromV1 = (data: v1Thread[]): formattedComment[] => { for (const item of data) { const val = item.comments, forkName = item.fork; - for (const key of Object.keys(val)) { - const value = val[key]; - if (!value) continue; + for (const value of val) { const tmpParam: formattedComment = { id: value.no, vpos: Math.floor(value.vposMs / 10), diff --git a/src/nico.ts b/src/nico.ts index 7b4d08d6..d4ef18be 100644 --- a/src/nico.ts +++ b/src/nico.ts @@ -50,9 +50,7 @@ const measureWidth = ( itemWidth = []; context.font = parseFont(comment.font, fontSize); let currentWidth = 0; - for (let i = 0; i < comment.content.length; i++) { - const item = comment.content[i]; - if (item === undefined) continue; + for (const item of comment.content) { const lines = item.content.split("\n"); context.font = parseFont(item.font || comment.font, fontSize); const width = []; diff --git a/src/typeGuard.ts b/src/typeGuard.ts index 8eebcc86..8eaf5d7a 100644 --- a/src/typeGuard.ts +++ b/src/typeGuard.ts @@ -114,14 +114,11 @@ const typeGuard = { ) return false; if (!(i as XMLDocument).documentElement.children) return false; - for ( - let index = 0; - index < (i as XMLDocument).documentElement.children.length; - index++ - ) { - const value = (i as XMLDocument).documentElement.children[index]; - if (!value || value.nodeName !== "chat") continue; - if (!typeAttributeVerify(value, ["vpos", "date"])) return false; + for (const element of Array.from( + (i as XMLDocument).documentElement.children + )) { + if (!element || element.nodeName !== "chat") continue; + if (!typeAttributeVerify(element, ["vpos", "date"])) return false; } return true; }, @@ -168,8 +165,8 @@ const typeGuard = { thread: (i: unknown): i is v1Thread => { if (!objectVerify(i, ["id", "fork", "commentCount", "comments"])) return false; - for (const item of Object.keys((i as v1Thread).comments)) { - if (!typeGuard.v1.comment((i as v1Thread).comments[item])) return false; + for (const value of (i as v1Thread).comments) { + if (!typeGuard.v1.comment(value)) return false; } return true; }, diff --git a/src/util.ts b/src/util.ts index cc91a6bf..f589425c 100644 --- a/src/util.ts +++ b/src/util.ts @@ -5,12 +5,14 @@ import { colors } from "@/definition/colors"; import type { configItem } from "@/@types/config"; import type { IComment } from "@/@types/IComment"; import type { + commentContentIndex, commentFont, formattedCommentWithFont, formattedCommentWithSize, parsedCommand, } from "@/@types/types"; import type { formattedComment } from "@/@types/format.formatted"; +import { commentFlashFont } from "@/@types/types"; /** * 当たり判定からコメントを配置できる場所を探す * @param {number} currentPos @@ -454,6 +456,7 @@ const parseCommand = (comment: formattedComment): parsedCommand => { fontSize: undefined, color: undefined, strokeColor: undefined, + wakuColor: undefined, font: undefined, full: false, ender: false, @@ -475,6 +478,15 @@ const parseCommand = (comment: formattedComment): parsedCommand => { } else if (typeGuard.comment.colorCode(match[1])) { result.strokeColor = match[1].slice(1); } + } else if ( + result.wakuColor === undefined && + (match = command.match(/^nico:waku:(.+)$/)) + ) { + if (typeGuard.comment.color(match[1])) { + result.wakuColor = colors[match[1]]; + } else if (typeGuard.comment.colorCode(match[1])) { + result.wakuColor = match[1].slice(1); + } } else if (result.loc === undefined && typeGuard.comment.loc(command)) { result.loc = command; } else if (result.size === undefined && typeGuard.comment.size(command)) { @@ -533,6 +545,52 @@ const ArrayEqual = (a: unknown[], b: unknown[]) => { return true; }; +const getFlashFontIndex = (part: string): commentContentIndex[] => { + const regex = { + simsunStrong: new RegExp(config.flashChar.simsunStrong), + simsunWeak: new RegExp(config.flashChar.simsunWeak), + gulim: new RegExp(config.flashChar.gulim), + gothic: new RegExp(config.flashChar.gothic), + }; + const index: commentContentIndex[] = []; + let match; + if ((match = regex.simsunStrong.exec(part)) !== null) { + index.push({ font: "simsunStrong", index: match.index }); + } + if ((match = regex.simsunWeak.exec(part)) !== null) { + index.push({ font: "simsunWeak", index: match.index }); + } + if ((match = regex.gulim.exec(part)) !== null) { + index.push({ font: "gulim", index: match.index }); + } + if ((match = regex.gothic.exec(part)) !== null) { + index.push({ font: "gothic", index: match.index }); + } + return index; +}; + +const getFlashFontName = (font: string): commentFlashFont => { + if (font.match("^simsun.+")) return "simsun"; + if (font === "gothic") return "defont"; + return font as commentFlashFont; +}; + +const getValue = (value: T | undefined | null, alternative: T): T => { + return value ?? alternative; +}; + +const nativeSort = (getter: (input: T) => number) => { + return (a: T, b: T) => { + if (getter(a) > getter(b)) { + return 1; + } else if (getter(a) < getter(b)) { + return -1; + } else { + return 0; + } + }; +}; + export { getPosY, getPosX, @@ -546,4 +604,8 @@ export { isFlashComment, parseCommandAndNicoScript, ArrayEqual, + getFlashFontIndex, + getFlashFontName, + getValue, + nativeSort, };