Skip to content

Commit c1d46bf

Browse files
committed
support HmiColor correctly
1 parent 836a60e commit c1d46bf

4 files changed

Lines changed: 157 additions & 64 deletions

File tree

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@node-projects/svghmi.webcomponent",
3-
"version": "1.2.0",
3+
"version": "1.3.0",
44
"description": "a svghmi webcomponent.",
55
"type": "module",
66
"main": "./dist/index.js",

src/Converter.ts

Lines changed: 104 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,70 @@
1-
function hexToHSL(hex: string) {
2-
if (hex.startsWith('#'))
3-
hex = hex.substring(1);
4-
let r = parseInt(hex.substr(1, 2), 16) / 255;
5-
let g = parseInt(hex.substr(3, 2), 16) / 255;
6-
let b = parseInt(hex.substr(5, 2), 16) / 255;
1+
type ParsedHmiColor = {
2+
alpha: string;
3+
red: string;
4+
green: string;
5+
blue: string;
6+
format: "hmi" | "css";
7+
};
8+
9+
export class HmiColorValue extends String {
10+
}
11+
12+
type HmiColorInput = string | String;
13+
14+
function parseHmiColor(col: HmiColorInput): ParsedHmiColor | undefined {
15+
const value = col.toString().trim();
16+
const hmiMatch = /^0x([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(value);
17+
if (hmiMatch != null) {
18+
return {
19+
alpha: hmiMatch[1],
20+
red: hmiMatch[2],
21+
green: hmiMatch[3],
22+
blue: hmiMatch[4],
23+
format: "hmi"
24+
};
25+
}
26+
27+
const cssMatch = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?$/i.exec(value);
28+
if (cssMatch != null) {
29+
return {
30+
alpha: cssMatch[4] ?? "FF",
31+
red: cssMatch[1],
32+
green: cssMatch[2],
33+
blue: cssMatch[3],
34+
format: "css"
35+
};
36+
}
37+
38+
return undefined;
39+
}
40+
41+
function hmiColorToCssColor(col: HmiColorInput) {
42+
const parsedColor = parseHmiColor(col);
43+
if (parsedColor == null)
44+
return col;
45+
return `#${parsedColor.red}${parsedColor.green}${parsedColor.blue}${parsedColor.alpha}`;
46+
}
47+
48+
function hmiColorToRgbColor(col: HmiColorInput) {
49+
const parsedColor = parseHmiColor(col);
50+
if (parsedColor == null)
51+
return col;
52+
return `#${parsedColor.red}${parsedColor.green}${parsedColor.blue}`;
53+
}
54+
55+
function hmiColorToAlpha(col: HmiColorInput) {
56+
const parsedColor = parseHmiColor(col);
57+
if (parsedColor == null)
58+
return "";
59+
return parsedColor.alpha;
60+
}
61+
62+
function colorToHSL(hex: HmiColorInput) {
63+
const parsedColor = parseHmiColor(hex);
64+
const value = hex.toString().replace(/^#/, "");
65+
const r = parseInt(parsedColor?.red ?? value.substring(0, 2), 16) / 255;
66+
const g = parseInt(parsedColor?.green ?? value.substring(2, 4), 16) / 255;
67+
const b = parseInt(parsedColor?.blue ?? value.substring(4, 6), 16) / 255;
768

869
let max = Math.max(r, g, b), min = Math.min(r, g, b);
970
let h = 0;
@@ -25,7 +86,7 @@ function hexToHSL(hex: string) {
2586
return { h, s, l };
2687
}
2788

28-
function hslToHex(h: number, s: number, l: number) {
89+
function hslToRgbHex(h: number, s: number, l: number) {
2990
function f(p: number, q: number, t: number) {
3091
if (t < 0) t += 1;
3192
if (t > 1) t -= 1;
@@ -44,6 +105,18 @@ function hslToHex(h: number, s: number, l: number) {
44105
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
45106
}
46107

108+
function formatIlluminatedColor(input: HmiColorInput, rgbHex: string) {
109+
const parsedColor = parseHmiColor(input);
110+
if (parsedColor == null)
111+
return rgbHex;
112+
113+
const rgb = rgbHex.substring(1);
114+
if (parsedColor.format == "hmi")
115+
return new HmiColorValue(`0x${parsedColor.alpha}${rgb}`);
116+
117+
return `#${rgb}${parsedColor.alpha}`;
118+
}
119+
47120
export class Converter {
48121
static IsString(txt: unknown) {
49122
return typeof txt == "string";
@@ -61,31 +134,24 @@ export class Converter {
61134
return arr.length;
62135
}
63136

64-
static RGB(col: string) {
65-
if (col.startsWith('#'))
66-
return col;
67-
return '#' + col.substring(4);
68-
}
69-
70-
static RGBA(col: string) {
71-
if (col.startsWith('#'))
72-
return col;
73-
return '#' + col.substring(4) + col.substring(2, 4);
74-
}
75-
76-
static Alpha(col: string) {
77-
return col.substring(2, 4);
78-
}
79-
80-
static Iluminate(input: string, deviation: number, low = '#FFFFFF', high = '#000000') {
137+
static RGB(col: HmiColorInput) {
138+
return hmiColorToRgbColor(col);
139+
}
140+
141+
static RGBA(col: HmiColorInput) {
142+
return hmiColorToCssColor(col);
143+
}
144+
145+
static Alpha(col: HmiColorInput) {
146+
return hmiColorToAlpha(col);
147+
}
148+
149+
static Iluminate(input: HmiColorInput, deviation: number, low: HmiColorInput = '#FFFFFF', high: HmiColorInput = '#000000') {
81150
deviation = Math.max(-1, Math.min(1, deviation)); // Clamp deviation to [-1, 1]
82151

83-
let hex = input;
84-
if (hex.startsWith("0x"))
85-
hex = hex.substring(4);
86-
let inputHSL = hexToHSL(hex);
87-
let lowHSL = hexToHSL(low);
88-
let highHSL = hexToHSL(high);
152+
let inputHSL = colorToHSL(input);
153+
let lowHSL = colorToHSL(low);
154+
let highHSL = colorToHSL(high);
89155

90156
let lowFactor = lowHSL.l;
91157
let highFactor = highHSL.l;
@@ -98,16 +164,16 @@ export class Converter {
98164
let adjustedL = inputHSL.l + deviation * (highFactor - lowFactor);
99165
adjustedL = Math.max(0, Math.min(1, adjustedL)); // Clamp L between 0 and 1
100166

101-
return hslToHex(inputHSL.h, inputHSL.s, adjustedL);
102-
}
103-
104-
static Darker(input: string, deviation: number) {
105-
return this.Iluminate(input, deviation);
167+
return formatIlluminatedColor(input, hslToRgbHex(inputHSL.h, inputHSL.s, adjustedL));
106168
}
107169

108-
static Lighter(input: string, deviation: number) {
109-
return this.Iluminate(input, -1 * deviation);
110-
}
170+
static Darker(input: HmiColorInput, deviation: number) {
171+
return this.Iluminate(input, deviation);
172+
}
173+
174+
static Lighter(input: HmiColorInput, deviation: number) {
175+
return this.Iluminate(input, -1 * deviation);
176+
}
111177

112178
static Bounds(input: number, min: number, max: number) {
113179
return Math.min(Math.max(input, min), max);

src/SvgHmi.ts

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
import { BaseCustomWebComponentConstructorAppend, css, html } from "@node-projects/base-custom-webcomponent";
2-
import { evalWithContext } from "./EvalWithContext.js";
3-
4-
export class SvgHmi extends BaseCustomWebComponentConstructorAppend {
1+
import { BaseCustomWebComponentConstructorAppend, css, html } from "@node-projects/base-custom-webcomponent";
2+
import { HmiColorValue } from "./Converter.js";
3+
import { evalWithContext } from "./EvalWithContext.js";
4+
5+
type SvgHmiPropertyDefinition = { name: string, type: string, default: unknown };
6+
7+
export class SvgHmi extends BaseCustomWebComponentConstructorAppend {
58
[key: string]: unknown;
69

710
public static override readonly style = css`
@@ -23,8 +26,8 @@ export class SvgHmi extends BaseCustomWebComponentConstructorAppend {
2326

2427
private _mutationObserver: MutationObserver;
2528

26-
public _svgHmiProperties: Map<string, { name: string, type: string, default: string }> = new Map();
27-
public _svgHmiLocalDefs: Map<string, { name: string, type: string, value: string }> = new Map();
29+
public _svgHmiProperties: Map<string, SvgHmiPropertyDefinition> = new Map();
30+
public _svgHmiLocalDefs: Map<string, { name: string, type: string, value: unknown }> = new Map();
2831
private _boundAttributes: { element: Element, elementParent?: Element, attribute: string, value: string }[] = [];
2932

3033
#src = "";
@@ -55,7 +58,7 @@ export class SvgHmi extends BaseCustomWebComponentConstructorAppend {
5558
else {
5659
const prp = this._svgHmiProperties.get(attributeName);
5760
if (prp != null) {
58-
this['__' + prp.name] = this.getAttribute(attributeName);
61+
this['__' + prp.name] = SvgHmi.parseValue(this.getAttribute(attributeName), prp.type);
5962
}
6063
}
6164
}
@@ -102,14 +105,14 @@ export class SvgHmi extends BaseCustomWebComponentConstructorAppend {
102105
continue;
103106
let nm = SvgHmi.camelToDashCase(name);
104107
let tp = n.getAttribute('type') ?? "";
105-
let d = n.getAttribute('default') ?? "";
108+
let d = SvgHmi.parseValue(n.getAttribute('default') ?? "", tp);
106109
this._svgHmiProperties.set(nm, { name: name, type: tp, default: d });
107110

108111
let val = this.getAttribute(nm);
109-
this['__' + name] = val ?? d;
112+
this['__' + name] = val == null ? d : SvgHmi.parseValue(val, tp);
110113

111114
if (this[name]) {
112-
this['__' + name] = this[name];
115+
this['__' + name] = SvgHmi.parseValue(this[name], tp);
113116
delete this[name];
114117
}
115118

@@ -118,8 +121,9 @@ export class SvgHmi extends BaseCustomWebComponentConstructorAppend {
118121
return this['__' + name];
119122
},
120123
set(newValue) {
121-
if (this['__' + name] !== newValue) {
122-
this['__' + name] = newValue;
124+
const parsedValue = SvgHmi.parseValue(newValue, tp);
125+
if (this['__' + name] !== parsedValue) {
126+
this['__' + name] = parsedValue;
123127
this._evaluateSvgBindings();
124128
}
125129
},
@@ -134,7 +138,7 @@ export class SvgHmi extends BaseCustomWebComponentConstructorAppend {
134138
if (name == null)
135139
continue;
136140
let tp = n.getAttribute('type') ?? "";
137-
let v = n.getAttribute('value') ?? "";
141+
let v = SvgHmi.parseValue(n.getAttribute('value') ?? "", tp);
138142
this._svgHmiLocalDefs.set(name, { name: name, type: tp, value: v });
139143
}
140144

@@ -166,15 +170,38 @@ export class SvgHmi extends BaseCustomWebComponentConstructorAppend {
166170
return text[0].toLowerCase() + text.substring(1).replace(/([A-Z])/g, (g) => `-${g[0].toLowerCase()}`);
167171
}
168172

169-
/*private parseValue(value: string, type: string) {
170-
switch (type) {
171-
case "HmiColor": {
173+
public static hmiColorToSvgColor(value: string) {
174+
const match = /^0x([0-9a-f]{2})([0-9a-f]{6})$/i.exec(value.trim());
175+
if (match == null)
176+
return value;
172177

173-
}
174-
}
178+
return `#${match[2]}${match[1]}`;
179+
}
180+
181+
private static parseValue(value: unknown, type: string) {
182+
if (value == null)
183+
return value;
175184

176-
return value;
177-
}*/
185+
switch (type) {
186+
case "number":
187+
return typeof value == "number" ? value : Number(value);
188+
case "boolean":
189+
return typeof value == "boolean" ? value : String(value).toLowerCase() == "true";
190+
case "string":
191+
return String(value);
192+
case "HmiColor":
193+
return value instanceof HmiColorValue ? value : new HmiColorValue(String(value));
194+
}
195+
196+
return value;
197+
}
198+
199+
private static formatBindingValue(value: unknown) {
200+
if (value instanceof HmiColorValue)
201+
return SvgHmi.hmiColorToSvgColor(value.toString());
202+
203+
return String(value ?? "");
204+
}
178205

179206
protected override _parseAttributesToProperties(noBindings?: boolean): void {
180207
super._parseAttributesToProperties();
@@ -186,17 +213,17 @@ export class SvgHmi extends BaseCustomWebComponentConstructorAppend {
186213
const name = b.element.getAttribute('name');
187214
const localDef = name == null ? undefined : this._svgHmiLocalDefs.get(name);
188215
if (localDef != null)
189-
localDef.value = String(evalWithContext(this, b.value) ?? "");
216+
localDef.value = SvgHmi.parseValue(evalWithContext(this, b.value), localDef.type);
190217
} else if (b.element.localName == "text" && b.element.namespaceURI == "http://svg.siemens.com/hmi/") {
191218
const par = b.elementParent ?? b.element.parentNode;
192219
if (par instanceof Element) {
193220
b.elementParent = par;
194-
par.textContent = String(evalWithContext(this, b.value) ?? "");
221+
par.textContent = SvgHmi.formatBindingValue(evalWithContext(this, b.value));
195222
}
196223
//this._svgHmiLocalDefs.get(b.element.getAttribute('name')).value = evalWithContext(this, b.value);
197224
} else {
198225
const val = evalWithContext(this, b.value);
199-
b.element.setAttribute(b.attribute, String(val ?? ""));
226+
b.element.setAttribute(b.attribute, SvgHmi.formatBindingValue(val));
200227
//if (b.element instanceof SVGElement)
201228
// b.element.style[b.attribute] = val;
202229
}

0 commit comments

Comments
 (0)