mirror of
https://github.com/felixfoertsch/EurKEY-macOS.git
synced 2026-04-16 06:28:28 +02:00
rebuild website, PDF generator to match keyboard viewer
remove hero section, add download button in nav, version-aware PDF download button. rename section title. rebuild ISO enter as single tall key spanning from row 3 upward. rebuild PDF generator: match viewer layout (ISO enter, fn key, correct sizes, arrow cluster, symbol labels), larger fonts, bold modifier labels. dead key pages now render full keyboards with base/shift compositions, catchy group names matched by terminator character. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,9 +17,7 @@
|
|||||||
<span>EurKEY</span>
|
<span>EurKEY</span>
|
||||||
</a>
|
</a>
|
||||||
<div class="nav-links">
|
<div class="nav-links">
|
||||||
<a href="#layout">Layout</a>
|
<a href="https://github.com/felixfoertsch/EurKEY-macOS/releases" class="btn btn--nav">Download</a>
|
||||||
<a href="#features">Features</a>
|
|
||||||
<a href="#install">Install</a>
|
|
||||||
<a href="https://github.com/felixfoertsch/EurKEY-macOS" class="nav-github" aria-label="GitHub">
|
<a href="https://github.com/felixfoertsch/EurKEY-macOS" class="nav-github" aria-label="GitHub">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||||
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/>
|
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/>
|
||||||
@@ -29,18 +27,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- HERO -->
|
|
||||||
<section class="hero">
|
|
||||||
<img src="img/icon.svg" alt="EurKEY icon" class="hero-icon" width="96" height="96">
|
|
||||||
<h1>The European Keyboard Layout for macOS</h1>
|
|
||||||
<p class="hero-sub">Dead keys for diacritics. ISO international layout. Designed for MacBook keyboards.</p>
|
|
||||||
<a href="https://github.com/felixfoertsch/EurKEY-macOS/releases" class="btn">Download on GitHub</a>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- LAYOUT PREVIEW -->
|
<!-- LAYOUT PREVIEW -->
|
||||||
<section id="layout" class="layout-preview">
|
<section id="layout" class="layout-preview">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h2>Layout Preview</h2>
|
<h2>EurKEY — The European Keyboard Layout</h2>
|
||||||
<p class="section-sub">The complete key map for each version, showing all modifier layers and dead key compositions.</p>
|
<p class="section-sub">The complete key map for each version, showing all modifier layers and dead key compositions.</p>
|
||||||
<div class="layout-controls">
|
<div class="layout-controls">
|
||||||
<div class="version-tabs">
|
<div class="version-tabs">
|
||||||
@@ -57,10 +47,7 @@
|
|||||||
<div class="dead-key-grid" id="dead-key-grid"></div>
|
<div class="dead-key-grid" id="dead-key-grid"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout-pdf-links">
|
<div class="layout-pdf-links">
|
||||||
<a href="pdf/eurkey-v1.3-layout.pdf">v1.3 PDF</a>
|
<a href="pdf/eurkey-v2.0-layout.pdf" id="pdf-download" class="btn btn--secondary">Download v2.0 PDF</a>
|
||||||
<a href="pdf/eurkey-v1.2-layout.pdf">v1.2 PDF</a>
|
|
||||||
<a href="pdf/eurkey-v1.4-layout.pdf">v1.4 PDF</a>
|
|
||||||
<a href="pdf/eurkey-v2.0-layout.pdf">v2.0 PDF</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -262,6 +262,13 @@ window.addEventListener("blur", () => {
|
|||||||
|
|
||||||
/* --- Version tabs --- */
|
/* --- Version tabs --- */
|
||||||
|
|
||||||
|
function updatePdfLink() {
|
||||||
|
const link = document.getElementById("pdf-download");
|
||||||
|
if (!link) return;
|
||||||
|
link.href = "pdf/eurkey-" + currentVersion + "-layout.pdf";
|
||||||
|
link.textContent = "Download " + currentVersion + " PDF";
|
||||||
|
}
|
||||||
|
|
||||||
function initTabs() {
|
function initTabs() {
|
||||||
const tabs = document.querySelectorAll(".version-tab");
|
const tabs = document.querySelectorAll(".version-tab");
|
||||||
tabs.forEach(tab => {
|
tabs.forEach(tab => {
|
||||||
@@ -274,6 +281,7 @@ function initTabs() {
|
|||||||
currentVersion = version;
|
currentVersion = version;
|
||||||
|
|
||||||
document.getElementById("dead-key-panel").hidden = true;
|
document.getElementById("dead-key-panel").hidden = true;
|
||||||
|
updatePdfLink();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
currentData = await loadVersion(version);
|
currentData = await loadVersion(version);
|
||||||
|
|||||||
@@ -93,40 +93,19 @@ img {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-links .btn--nav {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links .btn--nav:hover {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
.nav-github {
|
.nav-github {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hero */
|
|
||||||
.hero {
|
|
||||||
text-align: center;
|
|
||||||
padding: 5rem 1.5rem 4rem;
|
|
||||||
background: linear-gradient(180deg, #f0f4ff 0%, #fff 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-icon {
|
|
||||||
border-radius: 20px;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero h1 {
|
|
||||||
font-size: 2.5rem;
|
|
||||||
font-weight: 800;
|
|
||||||
letter-spacing: -0.02em;
|
|
||||||
line-height: 1.15;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
max-width: 600px;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-sub {
|
|
||||||
font-size: 1.15rem;
|
|
||||||
color: #5a6178;
|
|
||||||
max-width: 480px;
|
|
||||||
margin: 0 auto 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -145,6 +124,24 @@ img {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn--nav {
|
||||||
|
padding: 0.4rem 1rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn--secondary {
|
||||||
|
background: #fff;
|
||||||
|
color: #003399;
|
||||||
|
border: 1px solid #003399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn--secondary:hover {
|
||||||
|
background: #f0f4ff;
|
||||||
|
color: #002266;
|
||||||
|
}
|
||||||
|
|
||||||
/* Features */
|
/* Features */
|
||||||
.features {
|
.features {
|
||||||
padding: 4rem 0;
|
padding: 4rem 0;
|
||||||
@@ -650,14 +647,6 @@ img {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.hero h1 {
|
|
||||||
font-size: 1.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-sub {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feature-grid {
|
.feature-grid {
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
}
|
}
|
||||||
@@ -669,14 +658,6 @@ img {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero {
|
|
||||||
padding: 3rem 1.5rem 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero h1 {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feature-grid {
|
.feature-grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,34 +36,35 @@ KEYBOARD_ROWS = [
|
|||||||
(10, 1.0, "§"), (18, 1.0, "1"), (19, 1.0, "2"), (20, 1.0, "3"),
|
(10, 1.0, "§"), (18, 1.0, "1"), (19, 1.0, "2"), (20, 1.0, "3"),
|
||||||
(21, 1.0, "4"), (23, 1.0, "5"), (22, 1.0, "6"), (26, 1.0, "7"),
|
(21, 1.0, "4"), (23, 1.0, "5"), (22, 1.0, "6"), (26, 1.0, "7"),
|
||||||
(28, 1.0, "8"), (25, 1.0, "9"), (29, 1.0, "0"), (27, 1.0, "-"),
|
(28, 1.0, "8"), (25, 1.0, "9"), (29, 1.0, "0"), (27, 1.0, "-"),
|
||||||
(24, 1.0, "="), (None, 2.0, "Backspace"),
|
(24, 1.0, "="), (None, 1.5, "⌫"),
|
||||||
],
|
],
|
||||||
# Row 1: QWERTY row
|
# Row 1: QWERTY row (1.0u spacer at end for ISO enter)
|
||||||
[
|
[
|
||||||
(None, 1.5, "Tab"), (12, 1.0, "Q"), (13, 1.0, "W"), (14, 1.0, "E"),
|
(None, 1.5, "⇥"), (12, 1.0, "Q"), (13, 1.0, "W"), (14, 1.0, "E"),
|
||||||
(15, 1.0, "R"), (17, 1.0, "T"), (16, 1.0, "Y"), (32, 1.0, "U"),
|
(15, 1.0, "R"), (17, 1.0, "T"), (16, 1.0, "Y"), (32, 1.0, "U"),
|
||||||
(34, 1.0, "I"), (31, 1.0, "O"), (35, 1.0, "P"), (33, 1.0, "["),
|
(34, 1.0, "I"), (31, 1.0, "O"), (35, 1.0, "P"), (33, 1.0, "["),
|
||||||
(30, 1.0, "]"),
|
(30, 1.0, "]"), ("spacer", 1.0, ""),
|
||||||
],
|
],
|
||||||
# Row 2: Home row
|
# Row 2: Home row (0.75u enter spanning into row 1)
|
||||||
[
|
[
|
||||||
(None, 1.75, "Caps"), (0, 1.0, "A"), (1, 1.0, "S"), (2, 1.0, "D"),
|
(None, 1.75, "⇪"), (0, 1.0, "A"), (1, 1.0, "S"), (2, 1.0, "D"),
|
||||||
(3, 1.0, "F"), (5, 1.0, "G"), (4, 1.0, "H"), (38, 1.0, "J"),
|
(3, 1.0, "F"), (5, 1.0, "G"), (4, 1.0, "H"), (38, 1.0, "J"),
|
||||||
(40, 1.0, "K"), (37, 1.0, "L"), (41, 1.0, ";"), (39, 1.0, "'"),
|
(40, 1.0, "K"), (37, 1.0, "L"), (41, 1.0, ";"), (39, 1.0, "'"),
|
||||||
(42, 1.0, "\\"), (None, 1.75, "Enter"),
|
(42, 1.0, "\\"), ("enter", 0.75, "⏎"),
|
||||||
],
|
],
|
||||||
# Row 3: Bottom row
|
# Row 3: Bottom row
|
||||||
[
|
[
|
||||||
(None, 1.25, "Shift"), (50, 1.0, "`"), (6, 1.0, "Z"), (7, 1.0, "X"),
|
(None, 1.25, "⇧"), (50, 1.0, "`"), (6, 1.0, "Z"), (7, 1.0, "X"),
|
||||||
(8, 1.0, "C"), (9, 1.0, "V"), (11, 1.0, "B"), (45, 1.0, "N"),
|
(8, 1.0, "C"), (9, 1.0, "V"), (11, 1.0, "B"), (45, 1.0, "N"),
|
||||||
(46, 1.0, "M"), (43, 1.0, ","), (47, 1.0, "."), (44, 1.0, "/"),
|
(46, 1.0, "M"), (43, 1.0, ","), (47, 1.0, "."), (44, 1.0, "/"),
|
||||||
(None, 2.75, "Shift"),
|
(None, 2.25, "⇧"),
|
||||||
],
|
],
|
||||||
# Row 4: Modifier row
|
# Row 4: Modifier row
|
||||||
[
|
[
|
||||||
(None, 1.5, "Ctrl"), (None, 1.25, "⌥"), (None, 1.5, "⌘"),
|
(None, 1.0, "fn"), (None, 1.0, "⌃"), (None, 1.0, "⌥"),
|
||||||
(None, 6.0, ""), (None, 1.5, "⌘"), (None, 1.25, "⌥"),
|
(None, 1.25, "⌘"), (None, 5.0, ""),
|
||||||
(None, 1.5, "Ctrl"),
|
(None, 1.25, "⌘"), (None, 1.0, "⌥"),
|
||||||
|
("arrows", 3.0, ""),
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -176,7 +177,17 @@ class LayoutPDF(FPDF):
|
|||||||
kw = width * KU - KEY_GAP
|
kw = width * KU - KEY_GAP
|
||||||
kh = KU - KEY_GAP
|
kh = KU - KEY_GAP
|
||||||
|
|
||||||
if key_code is None or key_code not in TYPING_KEY_CODES:
|
if key_code == "spacer":
|
||||||
|
# invisible spacer — just advance x
|
||||||
|
pass
|
||||||
|
elif key_code == "enter":
|
||||||
|
# tall enter key spanning up into previous row
|
||||||
|
enter_h = 2 * KU - KEY_GAP
|
||||||
|
enter_y = y - KU
|
||||||
|
self._draw_mod_key(x, enter_y, kw, enter_h, label, key_code)
|
||||||
|
elif key_code == "arrows":
|
||||||
|
self._draw_arrow_cluster(x, y, kw, kh)
|
||||||
|
elif key_code is None or key_code not in TYPING_KEY_CODES:
|
||||||
self._draw_mod_key(x, y, kw, kh, label, key_code)
|
self._draw_mod_key(x, y, kw, kh, label, key_code)
|
||||||
else:
|
else:
|
||||||
self._draw_key(x, y, kw, kh, key_code, data)
|
self._draw_key(x, y, kw, kh, key_code, data)
|
||||||
@@ -213,12 +224,30 @@ class LayoutPDF(FPDF):
|
|||||||
self.set_draw_color(*C_KEY_BORDER)
|
self.set_draw_color(*C_KEY_BORDER)
|
||||||
self.rect(x, y, w, h, "DF")
|
self.rect(x, y, w, h, "DF")
|
||||||
|
|
||||||
# show physical label for modifier keys
|
if label:
|
||||||
if label and key_code is None:
|
self._font(14, bold=True)
|
||||||
self._font(8)
|
|
||||||
self._color((130, 130, 130))
|
self._color((130, 130, 130))
|
||||||
self.set_xy(x, y + h / 2 - 2)
|
self.set_xy(x, y + h / 2 - 3)
|
||||||
self.cell(w, 4, label, align="C")
|
self.cell(w, 6, label, align="C")
|
||||||
|
|
||||||
|
def _draw_arrow_cluster(self, x, y, w, h):
|
||||||
|
"""Draw inverted-T arrow keys."""
|
||||||
|
arrow_w = w / 3 - KEY_GAP * 0.67
|
||||||
|
arrow_h = h / 2 - KEY_GAP * 0.5
|
||||||
|
arrows_top = [("", arrow_w), ("▲", arrow_w), ("", arrow_w)]
|
||||||
|
arrows_bot = [("◀", arrow_w), ("▼", arrow_w), ("▶", arrow_w)]
|
||||||
|
for row_arrows, ay in [(arrows_top, y), (arrows_bot, y + h / 2)]:
|
||||||
|
ax = x
|
||||||
|
for label, aw in row_arrows:
|
||||||
|
if label:
|
||||||
|
self.set_fill_color(*C_MOD_BG)
|
||||||
|
self.set_draw_color(*C_KEY_BORDER)
|
||||||
|
self.rect(ax, ay, aw, arrow_h, "DF")
|
||||||
|
self._font(7)
|
||||||
|
self._color((130, 130, 130))
|
||||||
|
self.set_xy(ax, ay + arrow_h / 2 - 2)
|
||||||
|
self.cell(aw, 4, label, align="C")
|
||||||
|
ax += aw + KEY_GAP
|
||||||
|
|
||||||
def _draw_key(self, x, y, w, h, key_code, data):
|
def _draw_key(self, x, y, w, h, key_code, data):
|
||||||
"""Draw a typing key with 4 modifier layers."""
|
"""Draw a typing key with 4 modifier layers."""
|
||||||
@@ -250,40 +279,67 @@ class LayoutPDF(FPDF):
|
|||||||
self._color(color)
|
self._color(color)
|
||||||
|
|
||||||
if pos == "bottom_left":
|
if pos == "bottom_left":
|
||||||
self._font(12)
|
self._font(14)
|
||||||
self.set_xy(x + pad, mid_y - 0.5)
|
self.set_xy(x + pad, mid_y - 0.5)
|
||||||
self.cell(w / 2 - pad, h / 2, char)
|
self.cell(w / 2 - pad, h / 2, char)
|
||||||
elif pos == "top_left":
|
elif pos == "top_left":
|
||||||
self._font(9)
|
self._font(11)
|
||||||
self.set_xy(x + pad, y + pad)
|
self.set_xy(x + pad, y + pad)
|
||||||
self.cell(w / 2 - pad, 5, char)
|
self.cell(w / 2 - pad, 5.5, char)
|
||||||
elif pos == "bottom_right":
|
elif pos == "bottom_right":
|
||||||
self._font(9)
|
self._font(11)
|
||||||
self.set_xy(mid_x, mid_y - 0.5)
|
self.set_xy(mid_x, mid_y - 0.5)
|
||||||
self.cell(w / 2 - pad, h / 2, char, align="R")
|
self.cell(w / 2 - pad, h / 2, char, align="R")
|
||||||
elif pos == "top_right":
|
elif pos == "top_right":
|
||||||
self._font(9)
|
self._font(11)
|
||||||
self.set_xy(mid_x, y + pad)
|
self.set_xy(mid_x, y + pad)
|
||||||
self.cell(w / 2 - pad, 5, char, align="R")
|
self.cell(w / 2 - pad, 5.5, char, align="R")
|
||||||
|
|
||||||
|
# catchy names keyed by terminator character (stable across versions)
|
||||||
|
DEAD_KEY_NAMES_BY_TERMINATOR = {
|
||||||
|
"´": "The Acutes",
|
||||||
|
"`": "The Graves",
|
||||||
|
"^": "The Circumflexes",
|
||||||
|
"~": "The Tildes",
|
||||||
|
"¨": "The Umlauts",
|
||||||
|
"ˇ": "The Háčeks",
|
||||||
|
"¯": "The Macrons",
|
||||||
|
"˚": "The Rings & Dots",
|
||||||
|
"α": "The Greeks",
|
||||||
|
"√": "The Mathematicians",
|
||||||
|
"¬": "The Navigators",
|
||||||
|
"©": "The Navigators",
|
||||||
|
" ": "The Mathematicians",
|
||||||
|
}
|
||||||
|
|
||||||
|
def _find_dead_key_trigger(self, data, state_name):
|
||||||
|
"""Find which key combo triggers a dead key state."""
|
||||||
|
mod_names = {
|
||||||
|
"0": "", "1": "⇧ ", "2": "⇪ ", "3": "⌥ ",
|
||||||
|
"4": "⇧⌥ ", "5": "⇪⌥ ",
|
||||||
|
}
|
||||||
|
# map key codes to physical labels from KEYBOARD_ROWS
|
||||||
|
code_labels = {}
|
||||||
|
for row in KEYBOARD_ROWS:
|
||||||
|
for key_code, _, label in row:
|
||||||
|
if isinstance(key_code, int):
|
||||||
|
code_labels[str(key_code)] = label
|
||||||
|
for mod_idx, km in data["keyMaps"].items():
|
||||||
|
for kc, kd in km["keys"].items():
|
||||||
|
if kd.get("deadKey") == state_name:
|
||||||
|
prefix = mod_names.get(mod_idx, f"mod{mod_idx} ")
|
||||||
|
key_label = code_labels.get(kc, f"key{kc}")
|
||||||
|
return f"{prefix}{key_label}"
|
||||||
|
return state_name
|
||||||
|
|
||||||
def _draw_dead_key_pages(self, data):
|
def _draw_dead_key_pages(self, data):
|
||||||
"""Draw dead key composition tables on new pages."""
|
"""Draw dead key compositions as full keyboard layouts."""
|
||||||
dead_keys = data.get("deadKeys", {})
|
dead_keys = data.get("deadKeys", {})
|
||||||
if not dead_keys:
|
if not dead_keys:
|
||||||
return
|
return
|
||||||
|
|
||||||
actions = data.get("actions", {})
|
actions = data.get("actions", {})
|
||||||
|
|
||||||
self.add_page()
|
|
||||||
self._font(18, bold=True)
|
|
||||||
self._color((0, 0, 0))
|
|
||||||
self.set_xy(MARGIN, 8)
|
|
||||||
self.cell(0, 7, f"{self.layout_name} — Dead Key Compositions")
|
|
||||||
|
|
||||||
y = 20
|
|
||||||
col_w = 14 # width per composition entry
|
|
||||||
max_cols = int((self.w - 2 * MARGIN) / col_w)
|
|
||||||
|
|
||||||
for state_name in sorted(dead_keys.keys()):
|
for state_name in sorted(dead_keys.keys()):
|
||||||
dk = dead_keys[state_name]
|
dk = dead_keys[state_name]
|
||||||
terminator = dk.get("terminator", "")
|
terminator = dk.get("terminator", "")
|
||||||
@@ -291,55 +347,96 @@ class LayoutPDF(FPDF):
|
|||||||
if not compositions:
|
if not compositions:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# build readable composition list: (base_char, composed_char)
|
# build char → composed lookup
|
||||||
pairs = []
|
char_to_composed = {}
|
||||||
for action_id, composed in sorted(compositions.items()):
|
for action_id, composed in compositions.items():
|
||||||
base = actions.get(action_id, {}).get("none", action_id)
|
base = actions.get(action_id, {}).get("none", "")
|
||||||
base = safe_char(base)
|
if base:
|
||||||
composed = safe_char(composed)
|
char_to_composed[base] = composed
|
||||||
if base and composed:
|
|
||||||
pairs.append((base, composed))
|
|
||||||
|
|
||||||
# estimate space needed
|
# skip if no compositions mapped
|
||||||
num_rows = (len(pairs) + max_cols - 1) // max_cols
|
if not char_to_composed:
|
||||||
needed = 10 + num_rows * 5.5
|
continue
|
||||||
|
|
||||||
if y + needed > self.h - 10:
|
trigger = self._find_dead_key_trigger(data, state_name)
|
||||||
self.add_page()
|
catchy = self.DEAD_KEY_NAMES_BY_TERMINATOR.get(terminator, state_name)
|
||||||
y = 12
|
self.add_page()
|
||||||
|
|
||||||
# header
|
# title
|
||||||
self._font(9, bold=True)
|
self._font(18, bold=True)
|
||||||
self._color((0, 0, 0))
|
self._color((0, 0, 0))
|
||||||
self.set_xy(MARGIN, y)
|
self.set_xy(MARGIN, 7)
|
||||||
display = state_name
|
self.cell(0, 7, f"{self.layout_name} — {catchy} ({trigger}, terminator: {safe_char(terminator)})")
|
||||||
self.cell(0, 5, f"{display} (terminator: {safe_char(terminator)})")
|
|
||||||
y += 6
|
|
||||||
|
|
||||||
# composition grid
|
# draw keyboard with compositions
|
||||||
col = 0
|
kb_y = 24
|
||||||
for base, composed in pairs:
|
for row_idx, row in enumerate(KEYBOARD_ROWS):
|
||||||
cx = MARGIN + col * col_w
|
x = MARGIN
|
||||||
self._font(9)
|
y = kb_y + row_idx * KU
|
||||||
self._color((100, 100, 100))
|
for key_code, width, label in row:
|
||||||
self.set_xy(cx, y)
|
kw = width * KU - KEY_GAP
|
||||||
self.cell(5, 5, base)
|
kh = KU - KEY_GAP
|
||||||
self._color((0, 0, 0))
|
|
||||||
self.set_xy(cx + 4, y)
|
|
||||||
self.cell(9, 5, f"→{composed}")
|
|
||||||
|
|
||||||
col += 1
|
if key_code == "spacer":
|
||||||
if col >= max_cols:
|
pass
|
||||||
col = 0
|
elif key_code == "enter":
|
||||||
y += 5.5
|
enter_h = 2 * KU - KEY_GAP
|
||||||
|
enter_y = y - KU
|
||||||
|
self._draw_mod_key(x, enter_y, kw, enter_h, label, key_code)
|
||||||
|
elif key_code == "arrows":
|
||||||
|
self._draw_arrow_cluster(x, y, kw, kh)
|
||||||
|
elif not isinstance(key_code, int) or key_code not in TYPING_KEY_CODES:
|
||||||
|
self._draw_mod_key(x, y, kw, kh, label, key_code)
|
||||||
|
else:
|
||||||
|
self._draw_dead_composition_key(
|
||||||
|
x, y, kw, kh, key_code, data, char_to_composed,
|
||||||
|
)
|
||||||
|
|
||||||
if col > 0:
|
x += width * KU
|
||||||
y += 4.5
|
|
||||||
y += 3
|
|
||||||
|
|
||||||
if y > self.h - 15:
|
def _draw_dead_composition_key(self, x, y, w, h, key_code, data, char_to_composed):
|
||||||
self.add_page()
|
"""Draw a key showing dead key compositions for base and shift layers."""
|
||||||
y = 12
|
code_str = str(key_code)
|
||||||
|
km_base = data["keyMaps"].get(MOD_BASE, {}).get("keys", {})
|
||||||
|
km_shift = data["keyMaps"].get(MOD_SHIFT, {}).get("keys", {})
|
||||||
|
|
||||||
|
base_char = km_base.get(code_str, {}).get("output", "")
|
||||||
|
shift_char = km_shift.get(code_str, {}).get("output", "")
|
||||||
|
|
||||||
|
base_composed = char_to_composed.get(base_char, "")
|
||||||
|
shift_composed = char_to_composed.get(shift_char, "")
|
||||||
|
|
||||||
|
# background — highlight if any composition exists
|
||||||
|
has_comp = bool(base_composed or shift_composed)
|
||||||
|
bg = C_DEAD_BG if has_comp else C_KEY_BG
|
||||||
|
self.set_fill_color(*bg)
|
||||||
|
self.set_draw_color(*C_KEY_BORDER)
|
||||||
|
self.rect(x, y, w, h, "DF")
|
||||||
|
|
||||||
|
pad = 1.2
|
||||||
|
mid_y = y + h / 2
|
||||||
|
|
||||||
|
if base_composed:
|
||||||
|
self._font(14)
|
||||||
|
self._color((0, 0, 0))
|
||||||
|
self.set_xy(x + pad, mid_y - 0.5)
|
||||||
|
self.cell(w / 2 - pad, h / 2, safe_char(base_composed))
|
||||||
|
elif base_char:
|
||||||
|
self._font(14)
|
||||||
|
self._color((200, 200, 200))
|
||||||
|
self.set_xy(x + pad, mid_y - 0.5)
|
||||||
|
self.cell(w / 2 - pad, h / 2, safe_char(base_char))
|
||||||
|
|
||||||
|
if shift_composed:
|
||||||
|
self._font(11)
|
||||||
|
self._color((0, 40, 170))
|
||||||
|
self.set_xy(x + pad, y + pad)
|
||||||
|
self.cell(w / 2 - pad, 5.5, safe_char(shift_composed))
|
||||||
|
elif shift_char:
|
||||||
|
self._font(11)
|
||||||
|
self._color((200, 200, 200))
|
||||||
|
self.set_xy(x + pad, y + pad)
|
||||||
|
self.cell(w / 2 - pad, 5.5, safe_char(shift_char))
|
||||||
|
|
||||||
|
|
||||||
def generate_pdf(version, output_dir):
|
def generate_pdf(version, output_dir):
|
||||||
|
|||||||
Reference in New Issue
Block a user