diff --git a/scripts/build-bundle.sh b/scripts/build-bundle.sh index 9b73df1..7c88d3d 100755 --- a/scripts/build-bundle.sh +++ b/scripts/build-bundle.sh @@ -9,7 +9,8 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" -BUNDLE_DIR="${PROJECT_DIR}/EurKey-macOS.bundle" +BUILD_DIR="${PROJECT_DIR}/build" +BUNDLE_DIR="${BUILD_DIR}/EurKey-macOS.bundle" CONTENTS_DIR="${BUNDLE_DIR}/Contents" RESOURCES_DIR="${CONTENTS_DIR}/Resources" @@ -25,37 +26,27 @@ BUNDLE_NAME="EurKEY-macOS" # layout versions to include VERSIONS=("v1.2" "v1.3" "v1.4" "v2.0") +SRC_DIR="${PROJECT_DIR}/src" + echo "Building ${BUNDLE_NAME} ${VERSION}" echo "Bundle: ${BUNDLE_DIR}" echo -# --- validate that all required files exist --- -errors=0 +# --- assemble bundle from src/ --- +rm -rf "${BUNDLE_DIR}" +mkdir -p "${RESOURCES_DIR}" + for ver in "${VERSIONS[@]}"; do - keylayout="${RESOURCES_DIR}/EurKEY ${ver}.keylayout" - icns="${RESOURCES_DIR}/EurKEY ${ver}.icns" - if [[ ! -f "${keylayout}" ]]; then - echo "ERROR: missing ${keylayout}" - errors=$((errors + 1)) - fi - if [[ ! -f "${icns}" ]]; then - echo "ERROR: missing ${icns}" - errors=$((errors + 1)) - fi + cp "${SRC_DIR}/keylayouts/EurKEY ${ver}.keylayout" "${RESOURCES_DIR}/" + cp "${SRC_DIR}/icons/EurKEY ${ver}.icns" "${RESOURCES_DIR}/" done for lang in en de es; do - strings="${RESOURCES_DIR}/${lang}.lproj/InfoPlist.strings" - if [[ ! -f "${strings}" ]]; then - echo "ERROR: missing ${strings}" - errors=$((errors + 1)) - fi + mkdir -p "${RESOURCES_DIR}/${lang}.lproj" + cp "${SRC_DIR}/lproj/${lang}.lproj/InfoPlist.strings" "${RESOURCES_DIR}/${lang}.lproj/" done -if [[ $errors -gt 0 ]]; then - echo "FAILED: ${errors} missing file(s)" - exit 1 -fi +echo "Assembled bundle from src/" # --- generate Info.plist --- cat > "${CONTENTS_DIR}/Info.plist" << 'PLIST_HEADER' diff --git a/scripts/build-pdf.sh b/scripts/build-pdf.sh new file mode 100755 index 0000000..a09687f --- /dev/null +++ b/scripts/build-pdf.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# Generate keyboard layout PDFs from .keylayout files. +# Requires: fpdf2 (pip install fpdf2) +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +exec python3 "${SCRIPT_DIR}/generate_layout_pdf.py" "$@" diff --git a/scripts/create-dmg.sh b/scripts/create-dmg.sh index b6a5e8a..c55e0cd 100755 --- a/scripts/create-dmg.sh +++ b/scripts/create-dmg.sh @@ -1,16 +1,18 @@ #!/usr/bin/env bash # Create a .dmg installer for EurKEY-macOS keyboard layouts. # -# The DMG contains the keyboard layout bundle and a symlink to -# /Library/Keyboard Layouts/ for drag-and-drop installation. +# The DMG contains the keyboard layout bundle, layout PDFs, and a symlink +# to /Library/Keyboard Layouts/ for drag-and-drop installation. # -# Usage: bash scripts/create-dmg.sh [--version YYYY.MM.DD] +# Auto-builds the bundle and PDFs if missing. +# +# Usage: bash scripts/build-dmg.sh [--version YYYY.MM.DD] set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" -BUNDLE_DIR="${PROJECT_DIR}/EurKey-macOS.bundle" BUILD_DIR="${PROJECT_DIR}/build" +BUNDLE_DIR="${BUILD_DIR}/EurKey-macOS.bundle" # parse arguments VERSION="${1:-$(date +%Y.%m.%d)}" @@ -24,13 +26,16 @@ STAGING_DIR="${BUILD_DIR}/dmg-staging" echo "Creating DMG: ${DMG_NAME}" -# --- ensure bundle is built --- +# --- auto-build bundle if missing --- if [[ ! -f "${BUNDLE_DIR}/Contents/Info.plist" ]]; then - echo "ERROR: bundle not found at ${BUNDLE_DIR}" - echo "Run scripts/build-bundle.sh first" - exit 1 + echo "Bundle not found, building..." + bash "${SCRIPT_DIR}/build-bundle.sh" --version "${VERSION}" fi +# --- auto-build PDFs --- +echo "Building PDFs..." +bash "${SCRIPT_DIR}/build-pdf.sh" --output "${BUILD_DIR}" + # --- prepare staging directory --- rm -rf "${STAGING_DIR}" mkdir -p "${STAGING_DIR}" @@ -38,6 +43,9 @@ mkdir -p "${STAGING_DIR}" # copy the bundle cp -R "${BUNDLE_DIR}" "${STAGING_DIR}/" +# copy layout PDFs +cp "${BUILD_DIR}"/eurkey-*-layout.pdf "${STAGING_DIR}/" + # create symlink to installation target ln -s "/Library/Keyboard Layouts" "${STAGING_DIR}/Install Here (Keyboard Layouts)" diff --git a/scripts/generate_layout_pdf.py b/scripts/generate_layout_pdf.py index 0518c08..8f8fe9e 100644 --- a/scripts/generate_layout_pdf.py +++ b/scripts/generate_layout_pdf.py @@ -82,9 +82,12 @@ DISPLAY_LAYERS = [ ] # Layout dimensions (mm) -KU = 16 # key unit size +KU = 20 # key unit size KEY_GAP = 1 # gap between keys -MARGIN = 12 # page margin +MARGIN = 10 # page margin +# 15 keys × 20mm = 300mm + 2×10mm margins = 320mm → custom page width +PAGE_W = 320 +PAGE_H = 210 # A4 height # Colors (RGB tuples) C_KEY_BG = (242, 242, 242) @@ -92,22 +95,10 @@ C_KEY_BORDER = (190, 190, 190) C_DEAD_BG = (255, 238, 204) C_MOD_BG = (225, 225, 230) -# Font candidates (in priority order) -FONT_PATHS = [ - "/System/Library/Fonts/Supplemental/Arial Unicode.ttf", - "/System/Library/Fonts/SFNS.ttf", - "/System/Library/Fonts/Supplemental/Tahoma.ttf", - "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", - "/usr/share/fonts/TTF/DejaVuSans.ttf", -] - - -def find_font(): - """Find a system font with good Unicode coverage.""" - for path in FONT_PATHS: - if Path(path).exists(): - return path - return None +# Font paths (relative to project root) +FONT_DIR = Path(__file__).parent.parent / "fonts" / "iosevka" +FONT_REGULAR = FONT_DIR / "IosevkaFixed-Regular.ttf" +FONT_BOLD = FONT_DIR / "IosevkaFixed-Bold.ttf" def safe_char(c): @@ -139,24 +130,22 @@ def get_key_info(data, mod_idx, code_str): class LayoutPDF(FPDF): - def __init__(self, layout_name, font_path=None): - super().__init__(orientation="L", unit="mm", format="A4") + def __init__(self, layout_name): + super().__init__(orientation="L", unit="mm", format=(PAGE_H, PAGE_W)) self.layout_name = layout_name self.set_auto_page_break(auto=False) - self._has_unicode = False - if font_path: - try: - self.add_font("Layout", "", font_path) - self._has_unicode = True - except Exception as e: - print(f" Warning: could not load font {font_path}: {e}") + if not FONT_REGULAR.exists(): + print(f"ERROR: font not found: {FONT_REGULAR}") + print("Run: scripts/download-fonts.sh or see fonts/README.md") + sys.exit(1) + + self.add_font("Iosevka", "", str(FONT_REGULAR)) + if FONT_BOLD.exists(): + self.add_font("Iosevka", "B", str(FONT_BOLD)) def _font(self, size, bold=False): - if self._has_unicode: - self.set_font("Layout", "", size) - else: - self.set_font("Helvetica", "B" if bold else "", size) + self.set_font("Iosevka", "B" if bold else "", size) def _color(self, rgb): self.set_text_color(*rgb) @@ -170,7 +159,7 @@ class LayoutPDF(FPDF): self.add_page() # title - self._font(13, bold=True) + self._font(18, bold=True) self._color((0, 0, 0)) self.set_xy(MARGIN, 7) self.cell(0, 7, self.layout_name) @@ -195,9 +184,9 @@ class LayoutPDF(FPDF): x += width * KU def _draw_legend(self): - y = 16 + y = 17 x = MARGIN - self._font(5) + self._font(8) items = [ ((0, 0, 0), "Base"), ((0, 40, 170), "Shift"), @@ -226,7 +215,7 @@ class LayoutPDF(FPDF): # show physical label for modifier keys if label and key_code is None: - self._font(5) + self._font(8) self._color((130, 130, 130)) self.set_xy(x, y + h / 2 - 2) self.cell(w, 4, label, align="C") @@ -261,19 +250,19 @@ class LayoutPDF(FPDF): self._color(color) if pos == "bottom_left": - self._font(8) + self._font(12) self.set_xy(x + pad, mid_y - 0.5) self.cell(w / 2 - pad, h / 2, char) elif pos == "top_left": - self._font(5.5) + self._font(9) self.set_xy(x + pad, y + pad) self.cell(w / 2 - pad, 5, char) elif pos == "bottom_right": - self._font(5.5) + self._font(9) self.set_xy(mid_x, mid_y - 0.5) self.cell(w / 2 - pad, h / 2, char, align="R") elif pos == "top_right": - self._font(5.5) + self._font(9) self.set_xy(mid_x, y + pad) self.cell(w / 2 - pad, 5, char, align="R") @@ -286,13 +275,13 @@ class LayoutPDF(FPDF): actions = data.get("actions", {}) self.add_page() - self._font(12, bold=True) + 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 = 12 # width per composition entry + col_w = 14 # width per composition entry max_cols = int((self.w - 2 * MARGIN) / col_w) for state_name in sorted(dead_keys.keys()): @@ -313,14 +302,14 @@ class LayoutPDF(FPDF): # estimate space needed num_rows = (len(pairs) + max_cols - 1) // max_cols - needed = 10 + num_rows * 4.5 + needed = 10 + num_rows * 5.5 if y + needed > self.h - 10: self.add_page() y = 12 # header - self._font(7, bold=True) + self._font(9, bold=True) self._color((0, 0, 0)) self.set_xy(MARGIN, y) display = state_name @@ -331,18 +320,18 @@ class LayoutPDF(FPDF): col = 0 for base, composed in pairs: cx = MARGIN + col * col_w - self._font(5.5) + self._font(9) self._color((100, 100, 100)) self.set_xy(cx, y) - self.cell(5, 4, base) + self.cell(5, 5, base) self._color((0, 0, 0)) self.set_xy(cx + 4, y) - self.cell(7, 4, f"→{composed}") + self.cell(9, 5, f"→{composed}") col += 1 if col >= max_cols: col = 0 - y += 4.5 + y += 5.5 if col > 0: y += 4.5 @@ -355,13 +344,8 @@ class LayoutPDF(FPDF): def generate_pdf(version, output_dir): """Generate a PDF for the given layout version.""" - bundle_dir = ( - Path(__file__).parent.parent - / "EurKey-macOS.bundle" - / "Contents" - / "Resources" - ) - keylayout = bundle_dir / f"EurKEY {version}.keylayout" + src_dir = Path(__file__).parent.parent / "src" / "keylayouts" + keylayout = src_dir / f"EurKEY {version}.keylayout" if not keylayout.exists(): print(f"ERROR: {keylayout} not found") @@ -370,13 +354,7 @@ def generate_pdf(version, output_dir): print(f"Generating PDF for EurKEY {version}...") data = parse_keylayout(str(keylayout)) - font_path = find_font() - if font_path: - print(f" Using font: {Path(font_path).name}") - else: - print(" Warning: no Unicode font found, falling back to Helvetica (limited charset)") - - pdf = LayoutPDF(f"EurKEY {version}", font_path) + pdf = LayoutPDF(f"EurKEY {version}") pdf.generate(data) out = Path(output_dir) @@ -394,9 +372,10 @@ def main(): default=["v1.2", "v1.3", "v1.4", "v2.0"], help="Layout versions to generate (default: all)", ) + default_output = str(Path(__file__).parent.parent / "build") parser.add_argument( - "--output", "-o", default="docs", - help="Output directory (default: docs/)", + "--output", "-o", default=default_output, + help="Output directory (default: build/)", ) args = parser.parse_args() diff --git a/scripts/validate_layouts.py b/scripts/validate_layouts.py index 10d1048..fa699c5 100644 --- a/scripts/validate_layouts.py +++ b/scripts/validate_layouts.py @@ -18,7 +18,7 @@ from pathlib import Path sys.path.insert(0, str(Path(__file__).parent)) from parse_keylayout import parse_keylayout, TYPING_KEY_CODES, MODIFIER_LABELS, KEY_CODE_NAMES -BUNDLE_DIR = Path(__file__).parent.parent / "EurKey-macOS.bundle" / "Contents" / "Resources" +BUNDLE_DIR = Path(__file__).parent.parent / "build" / "EurKey-macOS.bundle" / "Contents" / "Resources" # modifier indices that contain meaningful typing output # (exclude index 6 = Command+Option and 7 = Control — these are system shortcuts)