(Install package: clbiwiki-portal-frame-pixel-stack-20260717 / js/FrameGeometry.js) |
(Install package: clbiwiki-portal-frame-fixed / js/FrameGeometry.js) |
||
| 1번째 줄: | 1번째 줄: | ||
/* ========================================= | /* ========================================= | ||
FrameGeometry | FrameGeometry | ||
대문 | 대문 프레임 공통 canvas 픽셀 렌더러 | ||
========================================= */ | ========================================= */ | ||
| 7번째 줄: | 7번째 줄: | ||
'use strict'; | 'use strict'; | ||
var FRAME = { | var FRAME = { | ||
BORDER:1, | BORDER:1, | ||
| 26번째 줄: | 25번째 줄: | ||
}; | }; | ||
var DIMENSIONS = { | var DIMENSIONS = { | ||
FRAME_SURFACE_TOP: | FRAME_SURFACE_TOP:1, | ||
FRAME_SURFACE_BOTTOM: | FRAME_SURFACE_BOTTOM:8, | ||
WELL_TOP: | WELL_TOP:8, | ||
WELL_BOTTOM: | WELL_BOTTOM:10, | ||
BUTTON_EDGE_TOP: | BUTTON_EDGE_TOP:10, | ||
BUTTON_SURFACE_TOP: | BUTTON_SURFACE_TOP:11, | ||
BUTTON_SURFACE_BOTTOM: | BUTTON_SURFACE_BOTTOM:31, | ||
BUTTON_EDGE_BOTTOM: | BUTTON_EDGE_BOTTOM:32, | ||
WELL2_TOP: | WELL2_TOP:32, | ||
WELL2_BOTTOM: | WELL2_BOTTOM:34, | ||
FRAME2_SURFACE_TOP: | FRAME2_SURFACE_TOP:34, | ||
FRAME2_SURFACE_BOTTOM: | FRAME2_SURFACE_BOTTOM:41, | ||
ROW_HEIGHT: | ROW_HEIGHT:42, | ||
OVERLAP: | OVERLAP:9, | ||
BUTTON_TOTAL:22, | |||
BOTTOM_START_ABS_Y:33 | |||
}; | }; | ||
var COL = { | |||
B:[5,5,5], | |||
S:[29,29,29], | |||
H:[85,85,85], | |||
D:[16,16,16], | |||
BG:[8,8,8], | |||
T:[226,226,226], | |||
TW:[255,255,255] | |||
}; | |||
var seamListeners = []; | |||
var scaleListeners = []; | |||
var shellScale = 1; | |||
DIMENSIONS.TOP_FRAME = DIMENSIONS.FRAME_SURFACE_BOTTOM; | DIMENSIONS.TOP_FRAME = DIMENSIONS.FRAME_SURFACE_BOTTOM; | ||
DIMENSIONS.BUTTON_TOP = DIMENSIONS.BUTTON_EDGE_TOP; | DIMENSIONS.BUTTON_TOP = DIMENSIONS.BUTTON_EDGE_TOP; | ||
DIMENSIONS.BUTTON_BOTTOM = DIMENSIONS.BUTTON_EDGE_BOTTOM; | DIMENSIONS.BUTTON_BOTTOM = DIMENSIONS.BUTTON_EDGE_BOTTOM; | ||
DIMENSIONS.BOTTOM_FRAME = DIMENSIONS.ROW_HEIGHT - DIMENSIONS.FRAME2_SURFACE_TOP; | DIMENSIONS.BOTTOM_FRAME = DIMENSIONS.ROW_HEIGHT - DIMENSIONS.FRAME2_SURFACE_TOP; | ||
function integer(value) { | function integer(value) { | ||
| 57번째 줄: | 65번째 줄: | ||
function snapWidth(raw) { | function snapWidth(raw) { | ||
return Math.floor(Number(raw) || 0); | return Math.max(0, Math.floor(Number(raw) || 0)); | ||
} | |||
function readWidth(node) { | |||
if (!node) return 0; | |||
return snapWidth(node.clientWidth || node.offsetWidth || 0); | |||
} | |||
function readCssShellScale() { | |||
var raw = ''; | |||
var value; | |||
try { | |||
raw = w.getComputedStyle(document.documentElement) | |||
.getPropertyValue('--clbi-shell-scale'); | |||
} catch (error) {} | |||
value = parseFloat(raw); | |||
return isFinite(value) && value > 0 ? value : 1; | |||
} | |||
function getShellScale() { | |||
var current = readCssShellScale(); | |||
if (Math.abs(current - shellScale) > 0.0001) shellScale = current; | |||
return shellScale; | |||
} | |||
function setShellScale(value) { | |||
var next = Number(value); | |||
var previous = shellScale; | |||
if (!isFinite(next) || next <= 0) next = 1; | |||
shellScale = next; | |||
document.documentElement.style.setProperty( | |||
'--portal-frame-counter-scale', | |||
String(1 / shellScale) | |||
); | |||
if (Math.abs(previous - shellScale) <= 0.0001) return shellScale; | |||
scaleListeners.slice().forEach(function (listener) { | |||
try { listener(shellScale, previous); } catch (error) {} | |||
}); | |||
try { | |||
document.dispatchEvent(new CustomEvent('framegeometryscale', { | |||
detail:{ scale:shellScale, previous:previous } | |||
})); | |||
} catch (error) {} | |||
return shellScale; | |||
} | |||
function onScale(listener) { | |||
if (typeof listener !== 'function') return function () {}; | |||
scaleListeners.push(listener); | |||
return function () { | |||
var index = scaleListeners.indexOf(listener); | |||
if (index !== -1) scaleListeners.splice(index, 1); | |||
}; | |||
} | |||
function readRenderWidth(node) { | |||
var layoutWidth = readWidth(node); | |||
if (!layoutWidth) return 0; | |||
return Math.max(1, Math.round(layoutWidth * getShellScale())); | |||
} | |||
function applyCounterScale(hosts, inner, logicalHeight, overlap) { | |||
var scale = getShellScale(); | |||
var inverse = 1 / scale; | |||
var hostHeight = logicalHeight / scale; | |||
var hostList = Array.isArray(hosts) ? hosts : [hosts]; | |||
hostList.forEach(function (host) { | |||
if (!host || !host.style) return; | |||
host.classList.add('portal-frame-counter-host'); | |||
host.style.setProperty('height', hostHeight + 'px', 'important'); | |||
host.style.setProperty('min-height', hostHeight + 'px', 'important'); | |||
host.style.setProperty('max-height', hostHeight + 'px', 'important'); | |||
host.style.setProperty('flex-basis', hostHeight + 'px', 'important'); | |||
}); | |||
if (hosts && !Array.isArray(hosts) && typeof overlap === 'number') { | |||
hosts.style.setProperty('margin-top', (-overlap / scale) + 'px', 'important'); | |||
} else if (Array.isArray(hosts) && hosts[0] && typeof overlap === 'number') { | |||
hosts[0].style.setProperty('margin-top', (-overlap / scale) + 'px', 'important'); | |||
} | |||
if (inner && inner.style) { | |||
inner.classList.add('portal-frame-counter-inner'); | |||
inner.style.setProperty('height', logicalHeight + 'px'); | |||
inner.style.setProperty('transform-origin', 'top left'); | |||
inner.style.setProperty('transform', 'scale(' + inverse + ')'); | |||
inner.setAttribute('data-frame-shell-scale', String(scale)); | |||
} | |||
return { scale:scale, inverse:inverse, hostHeight:hostHeight }; | |||
} | } | ||
function | /* | ||
* 검증본의 버튼 마스크. phase는 2단 접합의 홀짝 위상만 더한다. | |||
* slant: right | left | both | |||
*/ | |||
function buttonMask(BTN_W, ys, slant, phase) { | |||
var step = Math.floor((ys + (phase || 0)) / 2); | |||
var left; | |||
var right; | |||
if (slant === 'right') { | |||
left = 0; | |||
right = BTN_W - step; | |||
} else if (slant === 'left') { | |||
left = step; | |||
right = BTN_W; | |||
} else { | |||
left = step; | |||
right = BTN_W - step; | |||
} | |||
return { left:left, right:right }; | |||
} | } | ||
function | function buildButtonPixels(BTN_W, SURFACE_H, slant, phase) { | ||
return | var H = 1 + SURFACE_H + 1; | ||
var W = BTN_W + 2; | |||
[ | function io(x, y) { | ||
if (y < 0 || y >= H) return false; | |||
var ys = y - 1; | |||
var ysc = ys < 0 ? 0 : (ys >= SURFACE_H ? SURFACE_H - 1 : ys); | |||
var m = buttonMask(BTN_W, ysc, slant, phase || 0); | |||
return x >= m.left && x <= m.right; | |||
} | |||
var g = []; | |||
var src; | |||
var y; | |||
var x; | |||
for (y = 0; y < H; y++) { | |||
g[y] = []; | |||
for (x = 0; x < W; x++) { | |||
if (!io(x, y)) { | |||
g[y][x] = 'BG'; | |||
continue; | |||
} | |||
var edge = !(io(x, y - 1) && io(x, y + 1) && io(x - 1, y) && io(x + 1, y)); | |||
g[y][x] = edge ? 'B' : 'S'; | |||
} | |||
} | |||
src = g.map(function (row) { return row.slice(); }); | |||
for (y = 0; y < H; y++) { | |||
for (x = 0; x < W; x++) { | |||
if (src[y][x] !== 'S') continue; | |||
var tb = y > 0 && src[y - 1][x] === 'B'; | |||
var rb = x < W - 1 && src[y][x + 1] === 'B'; | |||
var bb = y < H - 1 && src[y + 1][x] === 'B'; | |||
var lb = x > 0 && src[y][x - 1] === 'B'; | |||
if (tb || rb) g[y][x] = 'H'; | |||
else if (bb || lb) g[y][x] = 'D'; | |||
} | |||
} | |||
return g; | |||
} | } | ||
function | function buildReverseButtonPixels(BTN_W, SURFACE_H, slant, phase) { | ||
var H = 1 + SURFACE_H + 1; | |||
var W = BTN_W + 2; | |||
function io(x, y) { | |||
[ | if (y < 0 || y >= H) return false; | ||
var ys = y - 1; | |||
var ysc = ys < 0 ? 0 : (ys >= SURFACE_H ? SURFACE_H - 1 : ys); | |||
var reversed = SURFACE_H - 1 - ysc; | |||
var m = buttonMask(BTN_W, reversed, slant, phase || 0); | |||
return x >= m.left && x <= m.right; | |||
} | |||
var g = []; | |||
var src; | |||
var y; | |||
var x; | |||
for (y = 0; y < H; y++) { | |||
g[y] = []; | |||
for (x = 0; x < W; x++) { | |||
if (!io(x, y)) { | |||
g[y][x] = 'BG'; | |||
continue; | |||
} | |||
var edge = !(io(x, y - 1) && io(x, y + 1) && io(x - 1, y) && io(x + 1, y)); | |||
g[y][x] = edge ? 'B' : 'S'; | |||
} | |||
} | |||
src = g.map(function (row) { return row.slice(); }); | |||
for (y = 0; y < H; y++) { | |||
for (x = 0; x < W; x++) { | |||
if (src[y][x] !== 'S') continue; | |||
var tb = y > 0 && src[y - 1][x] === 'B'; | |||
var rb = x < W - 1 && src[y][x + 1] === 'B'; | |||
var bb = y < H - 1 && src[y + 1][x] === 'B'; | |||
var lb = x > 0 && src[y][x - 1] === 'B'; | |||
if (tb || rb) g[y][x] = 'H'; | |||
else if (bb || lb) g[y][x] = 'D'; | |||
} | |||
} | |||
return g; | |||
} | } | ||
function | function createImage(ctx, width, height, fillCode) { | ||
var image = ctx.createImageData(width, height); | |||
var color = COL[fillCode || 'BG']; | |||
var i; | |||
[ | for (i = 0; i < image.data.length; i += 4) { | ||
[ | image.data[i] = color[0]; | ||
image.data[i + 1] = color[1]; | |||
image.data[i + 2] = color[2]; | |||
image.data[i + 3] = 255; | |||
} | |||
return image; | |||
} | } | ||
function | function setPixel(image, width, height, x, y, code) { | ||
var color; | |||
var index; | |||
x = integer(x); | |||
y = integer(y); | |||
if (x < 0 || x >= width || y < 0 || y >= height) return; | |||
color = COL[code] || COL.BG; | |||
index = (y * width + x) * 4; | |||
image.data[index] = color[0]; | |||
image.data[index + 1] = color[1]; | |||
image.data[index + 2] = color[2]; | |||
image.data[index + 3] = 255; | |||
} | } | ||
function | function fillRect(image, width, height, x, y, rectWidth, rectHeight, code) { | ||
var | var endX = Math.min(width, integer(x + rectWidth)); | ||
var endY = Math.min(height, integer(y + rectHeight)); | |||
var yy; | |||
var xx; | |||
x = Math.max(0, integer(x)); | |||
y = Math.max(0, integer(y)); | |||
for (yy = y; yy < endY; yy++) { | |||
for (xx = x; xx < endX; xx++) setPixel(image, width, height, xx, yy, code); | |||
} | |||
} | } | ||
function | function paintGrid(image, width, height, grid, offsetX, offsetY, skipBackground) { | ||
var | var y; | ||
var x; | |||
offsetX = integer(offsetX); | |||
offsetY = integer(offsetY); | |||
for (y = 0; y < grid.length; y++) { | |||
for (x = 0; x < grid[y].length; x++) { | |||
if (skipBackground && grid[y][x] === 'BG') continue; | |||
setPixel(image, width, height, offsetX + x, offsetY + y, grid[y][x]); | |||
} | |||
} | |||
} | } | ||
function | function paintRaisedRect(image, width, height, x, y, rectWidth, rectHeight) { | ||
var | var right = x + rectWidth - 1; | ||
var bottom = y + rectHeight - 1; | |||
var i; | |||
fillRect(image, width, height, x, y, rectWidth, rectHeight, 'B'); | |||
fillRect(image, width, height, x + 1, y + 1, rectWidth - 2, rectHeight - 2, 'S'); | |||
for (i = x + 1; i <= right - 1; i++) setPixel(image, width, height, i, bottom - 1, 'D'); | |||
for (i = y + 1; i <= bottom - 1; i++) setPixel(image, width, height, x + 1, i, 'D'); | |||
for (i = x + 1; i <= right - 1; i++) setPixel(image, width, height, i, y + 1, 'H'); | |||
for (i = y + 1; i <= bottom - 1; i++) setPixel(image, width, height, right - 1, i, 'H'); | |||
} | } | ||
function | function paintStandardFrame(image, width, height) { | ||
paintRaisedRect(image, width, height, 0, 0, width, height); | |||
fillRect( | |||
image, | |||
width, | |||
height, | |||
DIMENSIONS.WELL_TOP, | |||
DIMENSIONS.WELL_TOP, | |||
Math.max(0, width - DIMENSIONS.WELL_TOP * 2), | |||
DIMENSIONS.WELL2_BOTTOM - DIMENSIONS.WELL_TOP, | |||
'BG' | |||
); | |||
} | } | ||
function | function createCanvas(className, width, height) { | ||
var canvas = document.createElement('canvas'); | |||
canvas.className = className || 'portal-frame-canvas'; | |||
canvas.width = Math.max(1, integer(width)); | |||
canvas.height = Math.max(1, integer(height)); | |||
canvas.style.width = canvas.width + 'px'; | |||
canvas.style.height = canvas.height + 'px'; | |||
canvas.setAttribute('aria-hidden', 'true'); | |||
return canvas; | |||
} | } | ||
function | function drawLabel(ctx, text, x, y, hover, fontSize) { | ||
var family = 'Galmuri11, sans-serif'; | |||
try { | |||
family = w.getComputedStyle(ctx.canvas).fontFamily || family; | |||
} catch (error) {} | |||
ctx.save(); | |||
ctx.font = '700 ' + integer(fontSize || 12) + 'px ' + family; | |||
ctx.fillStyle = hover ? FRAME.COLORS.label.replace('e2e2e2', 'ffffff') : FRAME.COLORS.label; | |||
if (hover) ctx.fillStyle = '#ffffff'; | |||
ctx.textAlign = 'center'; | |||
ctx.textBaseline = 'middle'; | |||
ctx.fillText(String(text || ''), integer(x), integer(y)); | |||
ctx.restore(); | |||
} | } | ||
| 147번째 줄: | 366번째 줄: | ||
var frame = w.CLBI.frame; | var frame = w.CLBI.frame; | ||
var copy = {}; | var copy = {}; | ||
Object.keys(next || {}).forEach(function (key) { copy[key] = next[key]; }); | |||
Object.keys(next || {}).forEach(function (key) { | |||
copy.revision = integer((frame.seams && frame.seams.revision) || 0) + 1; | copy.revision = integer((frame.seams && frame.seams.revision) || 0) + 1; | ||
frame.seams = copy; | frame.seams = copy; | ||
seamListeners.slice().forEach(function (listener) { | |||
try { listener(copy); } catch (error) {} | try { listener(copy); } catch (error) {} | ||
}); | }); | ||
try { | try { | ||
document.dispatchEvent(new CustomEvent('framegeometryseams', { detail:copy })); | document.dispatchEvent(new CustomEvent('framegeometryseams', { detail:copy })); | ||
} catch (error) {} | } catch (error) {} | ||
if (w.mw && w.mw.hook) w.mw.hook('frame.geometry.seams').fire(copy); | |||
if (w.mw && w.mw.hook) | |||
return copy; | return copy; | ||
} | } | ||
| 170번째 줄: | 381번째 줄: | ||
function onSeams(listener) { | function onSeams(listener) { | ||
if (typeof listener !== 'function') return function () {}; | if (typeof listener !== 'function') return function () {}; | ||
seamListeners.push(listener); | |||
if (w.CLBI.frame.seams && w.CLBI.frame.seams.width) | if (w.CLBI.frame.seams && w.CLBI.frame.seams.width) listener(w.CLBI.frame.seams); | ||
return function () { | return function () { | ||
var index = | var index = seamListeners.indexOf(listener); | ||
if (index !== -1) | if (index !== -1) seamListeners.splice(index, 1); | ||
}; | }; | ||
} | } | ||
| 184번째 줄: | 393번째 줄: | ||
FRAME:FRAME, | FRAME:FRAME, | ||
DIMENSIONS:DIMENSIONS, | DIMENSIONS:DIMENSIONS, | ||
COL:COL, | |||
integer:integer, | |||
snapWidth:snapWidth, | snapWidth:snapWidth, | ||
readWidth:readWidth, | readWidth:readWidth, | ||
readRenderWidth:readRenderWidth, | |||
getShellScale:getShellScale, | |||
setShellScale:setShellScale, | |||
onScale:onScale, | |||
applyCounterScale:applyCounterScale, | |||
buttonMask:buttonMask, | |||
buildButtonPixels:buildButtonPixels, | |||
buildReverseButtonPixels:buildReverseButtonPixels, | |||
createImage:createImage, | |||
setPixel:setPixel, | |||
fillRect:fillRect, | |||
paintGrid:paintGrid, | |||
paintRaisedRect:paintRaisedRect, | |||
paintStandardFrame:paintStandardFrame, | |||
createCanvas:createCanvas, | |||
drawLabel:drawLabel, | |||
setSeams:setSeams, | setSeams:setSeams, | ||
onSeams:onSeams, | onSeams:onSeams, | ||
seams:{} | seams:{} | ||
}; | }; | ||
setShellScale(readCssShellScale()); | |||
}(window, document)); | }(window, document)); | ||
2026년 7월 17일 (금) 17:49 기준 최신판
/* =========================================
FrameGeometry
대문 프레임 공통 canvas 픽셀 렌더러
========================================= */
(function (w, document) {
'use strict';
var FRAME = {
BORDER:1,
BEVEL:1,
SLANT_RISE:2,
SLANT_RUN:1,
BUTTON_FACE:20,
FRAME_FACE:7,
GAP:2,
COLORS:{
border:'#050505',
surface:'#1d1d1d',
well:'#080808',
highlight:'#555555',
shadow:'#101010',
label:'#e2e2e2'
}
};
var DIMENSIONS = {
FRAME_SURFACE_TOP:1,
FRAME_SURFACE_BOTTOM:8,
WELL_TOP:8,
WELL_BOTTOM:10,
BUTTON_EDGE_TOP:10,
BUTTON_SURFACE_TOP:11,
BUTTON_SURFACE_BOTTOM:31,
BUTTON_EDGE_BOTTOM:32,
WELL2_TOP:32,
WELL2_BOTTOM:34,
FRAME2_SURFACE_TOP:34,
FRAME2_SURFACE_BOTTOM:41,
ROW_HEIGHT:42,
OVERLAP:9,
BUTTON_TOTAL:22,
BOTTOM_START_ABS_Y:33
};
var COL = {
B:[5,5,5],
S:[29,29,29],
H:[85,85,85],
D:[16,16,16],
BG:[8,8,8],
T:[226,226,226],
TW:[255,255,255]
};
var seamListeners = [];
var scaleListeners = [];
var shellScale = 1;
DIMENSIONS.TOP_FRAME = DIMENSIONS.FRAME_SURFACE_BOTTOM;
DIMENSIONS.BUTTON_TOP = DIMENSIONS.BUTTON_EDGE_TOP;
DIMENSIONS.BUTTON_BOTTOM = DIMENSIONS.BUTTON_EDGE_BOTTOM;
DIMENSIONS.BOTTOM_FRAME = DIMENSIONS.ROW_HEIGHT - DIMENSIONS.FRAME2_SURFACE_TOP;
function integer(value) {
return Math.round(Number(value) || 0);
}
function snapWidth(raw) {
return Math.max(0, Math.floor(Number(raw) || 0));
}
function readWidth(node) {
if (!node) return 0;
return snapWidth(node.clientWidth || node.offsetWidth || 0);
}
function readCssShellScale() {
var raw = '';
var value;
try {
raw = w.getComputedStyle(document.documentElement)
.getPropertyValue('--clbi-shell-scale');
} catch (error) {}
value = parseFloat(raw);
return isFinite(value) && value > 0 ? value : 1;
}
function getShellScale() {
var current = readCssShellScale();
if (Math.abs(current - shellScale) > 0.0001) shellScale = current;
return shellScale;
}
function setShellScale(value) {
var next = Number(value);
var previous = shellScale;
if (!isFinite(next) || next <= 0) next = 1;
shellScale = next;
document.documentElement.style.setProperty(
'--portal-frame-counter-scale',
String(1 / shellScale)
);
if (Math.abs(previous - shellScale) <= 0.0001) return shellScale;
scaleListeners.slice().forEach(function (listener) {
try { listener(shellScale, previous); } catch (error) {}
});
try {
document.dispatchEvent(new CustomEvent('framegeometryscale', {
detail:{ scale:shellScale, previous:previous }
}));
} catch (error) {}
return shellScale;
}
function onScale(listener) {
if (typeof listener !== 'function') return function () {};
scaleListeners.push(listener);
return function () {
var index = scaleListeners.indexOf(listener);
if (index !== -1) scaleListeners.splice(index, 1);
};
}
function readRenderWidth(node) {
var layoutWidth = readWidth(node);
if (!layoutWidth) return 0;
return Math.max(1, Math.round(layoutWidth * getShellScale()));
}
function applyCounterScale(hosts, inner, logicalHeight, overlap) {
var scale = getShellScale();
var inverse = 1 / scale;
var hostHeight = logicalHeight / scale;
var hostList = Array.isArray(hosts) ? hosts : [hosts];
hostList.forEach(function (host) {
if (!host || !host.style) return;
host.classList.add('portal-frame-counter-host');
host.style.setProperty('height', hostHeight + 'px', 'important');
host.style.setProperty('min-height', hostHeight + 'px', 'important');
host.style.setProperty('max-height', hostHeight + 'px', 'important');
host.style.setProperty('flex-basis', hostHeight + 'px', 'important');
});
if (hosts && !Array.isArray(hosts) && typeof overlap === 'number') {
hosts.style.setProperty('margin-top', (-overlap / scale) + 'px', 'important');
} else if (Array.isArray(hosts) && hosts[0] && typeof overlap === 'number') {
hosts[0].style.setProperty('margin-top', (-overlap / scale) + 'px', 'important');
}
if (inner && inner.style) {
inner.classList.add('portal-frame-counter-inner');
inner.style.setProperty('height', logicalHeight + 'px');
inner.style.setProperty('transform-origin', 'top left');
inner.style.setProperty('transform', 'scale(' + inverse + ')');
inner.setAttribute('data-frame-shell-scale', String(scale));
}
return { scale:scale, inverse:inverse, hostHeight:hostHeight };
}
/*
* 검증본의 버튼 마스크. phase는 2단 접합의 홀짝 위상만 더한다.
* slant: right | left | both
*/
function buttonMask(BTN_W, ys, slant, phase) {
var step = Math.floor((ys + (phase || 0)) / 2);
var left;
var right;
if (slant === 'right') {
left = 0;
right = BTN_W - step;
} else if (slant === 'left') {
left = step;
right = BTN_W;
} else {
left = step;
right = BTN_W - step;
}
return { left:left, right:right };
}
function buildButtonPixels(BTN_W, SURFACE_H, slant, phase) {
var H = 1 + SURFACE_H + 1;
var W = BTN_W + 2;
function io(x, y) {
if (y < 0 || y >= H) return false;
var ys = y - 1;
var ysc = ys < 0 ? 0 : (ys >= SURFACE_H ? SURFACE_H - 1 : ys);
var m = buttonMask(BTN_W, ysc, slant, phase || 0);
return x >= m.left && x <= m.right;
}
var g = [];
var src;
var y;
var x;
for (y = 0; y < H; y++) {
g[y] = [];
for (x = 0; x < W; x++) {
if (!io(x, y)) {
g[y][x] = 'BG';
continue;
}
var edge = !(io(x, y - 1) && io(x, y + 1) && io(x - 1, y) && io(x + 1, y));
g[y][x] = edge ? 'B' : 'S';
}
}
src = g.map(function (row) { return row.slice(); });
for (y = 0; y < H; y++) {
for (x = 0; x < W; x++) {
if (src[y][x] !== 'S') continue;
var tb = y > 0 && src[y - 1][x] === 'B';
var rb = x < W - 1 && src[y][x + 1] === 'B';
var bb = y < H - 1 && src[y + 1][x] === 'B';
var lb = x > 0 && src[y][x - 1] === 'B';
if (tb || rb) g[y][x] = 'H';
else if (bb || lb) g[y][x] = 'D';
}
}
return g;
}
function buildReverseButtonPixels(BTN_W, SURFACE_H, slant, phase) {
var H = 1 + SURFACE_H + 1;
var W = BTN_W + 2;
function io(x, y) {
if (y < 0 || y >= H) return false;
var ys = y - 1;
var ysc = ys < 0 ? 0 : (ys >= SURFACE_H ? SURFACE_H - 1 : ys);
var reversed = SURFACE_H - 1 - ysc;
var m = buttonMask(BTN_W, reversed, slant, phase || 0);
return x >= m.left && x <= m.right;
}
var g = [];
var src;
var y;
var x;
for (y = 0; y < H; y++) {
g[y] = [];
for (x = 0; x < W; x++) {
if (!io(x, y)) {
g[y][x] = 'BG';
continue;
}
var edge = !(io(x, y - 1) && io(x, y + 1) && io(x - 1, y) && io(x + 1, y));
g[y][x] = edge ? 'B' : 'S';
}
}
src = g.map(function (row) { return row.slice(); });
for (y = 0; y < H; y++) {
for (x = 0; x < W; x++) {
if (src[y][x] !== 'S') continue;
var tb = y > 0 && src[y - 1][x] === 'B';
var rb = x < W - 1 && src[y][x + 1] === 'B';
var bb = y < H - 1 && src[y + 1][x] === 'B';
var lb = x > 0 && src[y][x - 1] === 'B';
if (tb || rb) g[y][x] = 'H';
else if (bb || lb) g[y][x] = 'D';
}
}
return g;
}
function createImage(ctx, width, height, fillCode) {
var image = ctx.createImageData(width, height);
var color = COL[fillCode || 'BG'];
var i;
for (i = 0; i < image.data.length; i += 4) {
image.data[i] = color[0];
image.data[i + 1] = color[1];
image.data[i + 2] = color[2];
image.data[i + 3] = 255;
}
return image;
}
function setPixel(image, width, height, x, y, code) {
var color;
var index;
x = integer(x);
y = integer(y);
if (x < 0 || x >= width || y < 0 || y >= height) return;
color = COL[code] || COL.BG;
index = (y * width + x) * 4;
image.data[index] = color[0];
image.data[index + 1] = color[1];
image.data[index + 2] = color[2];
image.data[index + 3] = 255;
}
function fillRect(image, width, height, x, y, rectWidth, rectHeight, code) {
var endX = Math.min(width, integer(x + rectWidth));
var endY = Math.min(height, integer(y + rectHeight));
var yy;
var xx;
x = Math.max(0, integer(x));
y = Math.max(0, integer(y));
for (yy = y; yy < endY; yy++) {
for (xx = x; xx < endX; xx++) setPixel(image, width, height, xx, yy, code);
}
}
function paintGrid(image, width, height, grid, offsetX, offsetY, skipBackground) {
var y;
var x;
offsetX = integer(offsetX);
offsetY = integer(offsetY);
for (y = 0; y < grid.length; y++) {
for (x = 0; x < grid[y].length; x++) {
if (skipBackground && grid[y][x] === 'BG') continue;
setPixel(image, width, height, offsetX + x, offsetY + y, grid[y][x]);
}
}
}
function paintRaisedRect(image, width, height, x, y, rectWidth, rectHeight) {
var right = x + rectWidth - 1;
var bottom = y + rectHeight - 1;
var i;
fillRect(image, width, height, x, y, rectWidth, rectHeight, 'B');
fillRect(image, width, height, x + 1, y + 1, rectWidth - 2, rectHeight - 2, 'S');
for (i = x + 1; i <= right - 1; i++) setPixel(image, width, height, i, bottom - 1, 'D');
for (i = y + 1; i <= bottom - 1; i++) setPixel(image, width, height, x + 1, i, 'D');
for (i = x + 1; i <= right - 1; i++) setPixel(image, width, height, i, y + 1, 'H');
for (i = y + 1; i <= bottom - 1; i++) setPixel(image, width, height, right - 1, i, 'H');
}
function paintStandardFrame(image, width, height) {
paintRaisedRect(image, width, height, 0, 0, width, height);
fillRect(
image,
width,
height,
DIMENSIONS.WELL_TOP,
DIMENSIONS.WELL_TOP,
Math.max(0, width - DIMENSIONS.WELL_TOP * 2),
DIMENSIONS.WELL2_BOTTOM - DIMENSIONS.WELL_TOP,
'BG'
);
}
function createCanvas(className, width, height) {
var canvas = document.createElement('canvas');
canvas.className = className || 'portal-frame-canvas';
canvas.width = Math.max(1, integer(width));
canvas.height = Math.max(1, integer(height));
canvas.style.width = canvas.width + 'px';
canvas.style.height = canvas.height + 'px';
canvas.setAttribute('aria-hidden', 'true');
return canvas;
}
function drawLabel(ctx, text, x, y, hover, fontSize) {
var family = 'Galmuri11, sans-serif';
try {
family = w.getComputedStyle(ctx.canvas).fontFamily || family;
} catch (error) {}
ctx.save();
ctx.font = '700 ' + integer(fontSize || 12) + 'px ' + family;
ctx.fillStyle = hover ? FRAME.COLORS.label.replace('e2e2e2', 'ffffff') : FRAME.COLORS.label;
if (hover) ctx.fillStyle = '#ffffff';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(String(text || ''), integer(x), integer(y));
ctx.restore();
}
function setSeams(next) {
var frame = w.CLBI.frame;
var copy = {};
Object.keys(next || {}).forEach(function (key) { copy[key] = next[key]; });
copy.revision = integer((frame.seams && frame.seams.revision) || 0) + 1;
frame.seams = copy;
seamListeners.slice().forEach(function (listener) {
try { listener(copy); } catch (error) {}
});
try {
document.dispatchEvent(new CustomEvent('framegeometryseams', { detail:copy }));
} catch (error) {}
if (w.mw && w.mw.hook) w.mw.hook('frame.geometry.seams').fire(copy);
return copy;
}
function onSeams(listener) {
if (typeof listener !== 'function') return function () {};
seamListeners.push(listener);
if (w.CLBI.frame.seams && w.CLBI.frame.seams.width) listener(w.CLBI.frame.seams);
return function () {
var index = seamListeners.indexOf(listener);
if (index !== -1) seamListeners.splice(index, 1);
};
}
w.CLBI = w.CLBI || {};
w.CLBI.frame = {
FRAME:FRAME,
DIMENSIONS:DIMENSIONS,
COL:COL,
integer:integer,
snapWidth:snapWidth,
readWidth:readWidth,
readRenderWidth:readRenderWidth,
getShellScale:getShellScale,
setShellScale:setShellScale,
onScale:onScale,
applyCounterScale:applyCounterScale,
buttonMask:buttonMask,
buildButtonPixels:buildButtonPixels,
buildReverseButtonPixels:buildReverseButtonPixels,
createImage:createImage,
setPixel:setPixel,
fillRect:fillRect,
paintGrid:paintGrid,
paintRaisedRect:paintRaisedRect,
paintStandardFrame:paintStandardFrame,
createCanvas:createCanvas,
drawLabel:drawLabel,
setSeams:setSeams,
onSeams:onSeams,
seams:{}
};
setShellScale(readCssShellScale());
}(window, document));