From bb5bc1daf88cf73d176dddfa6476b1269d85a2ba Mon Sep 17 00:00:00 2001 From: coah <100182500+coah80@users.noreply.github.com> Date: Tue, 31 Mar 2026 13:42:22 -0500 Subject: [PATCH] last hurrah from me --- .cursor/rules/MENU_PORTING_GUIDE.md | 190 - ...-18f599ff-94e3-498e-8842-b453d880565d.json | 14 - .../CrossPlatform/CrossPlatformMain.cpp | 85 +- .../CrossPlatform/scenes/AnvilScene.cpp | 1 + .../scenes/BrewingStandScene.cpp | 1 + .../CrossPlatform/scenes/ChestScene.cpp | 2 + .../CrossPlatform/scenes/ControlsScene.cpp | 6 +- .../CrossPlatform/scenes/CraftingScene.cpp | 2 + .../CrossPlatform/scenes/CreateWorldScene.cpp | 184 + .../CrossPlatform/scenes/CreativeScene.cpp | 1 + .../CrossPlatform/scenes/DLCScene.cpp | 7 +- .../CrossPlatform/scenes/DeathMenuScene.cpp | 1 + .../CrossPlatform/scenes/DispenserScene.cpp | 1 + .../CrossPlatform/scenes/EnchantingScene.cpp | 1 + .../CrossPlatform/scenes/FurnaceScene.cpp | 1 + .../CrossPlatform/scenes/GameMenuScenes.cpp | 68 +- .../scenes/HelpAndOptionsScene.cpp | 1 + .../scenes/HowToPlayPageScene.cpp | 1 + .../CrossPlatform/scenes/HowToPlayScene.cpp | 1 + .../CrossPlatform/scenes/InventoryScene.cpp | 1 + .../CrossPlatform/scenes/JoinMenuScene.cpp | 18 +- .../CrossPlatform/scenes/LceStyles.h | 28 +- .../scenes/LeaderboardsScene.cpp | 1 + .../CrossPlatform/scenes/LoadMenuScene.cpp | 42 + .../CrossPlatform/scenes/LoadOrJoinScene.cpp | 1 + .../CrossPlatform/scenes/MiscScenes.cpp | 14 +- .../CrossPlatform/scenes/PauseMenuScene.cpp | 12 +- .../scenes/SettingsAudioScene.cpp | 10 +- .../scenes/SettingsControlScene.cpp | 10 +- .../scenes/SettingsGraphicsScene.cpp | 32 +- .../CrossPlatform/scenes/SettingsHubScene.cpp | 6 +- .../scenes/SettingsOptionsScene.cpp | 21 +- .../CrossPlatform/scenes/SettingsUIScene.cpp | 18 +- .../CrossPlatform/scenes/SkinSelectScene.cpp | 1 + .../CrossPlatform/scenes/TradingScene.cpp | 1 + .../Dev/RenderVk/VulkanBootstrapApp.h | 4 + .../Dev/RenderVk/VulkanBootstrapAppRender.cpp | 8 + .../Dev/RenderVk/VulkanRenderManager.cpp | 34 +- Minecraft.Client/ThirdParty/stb_truetype.h | 5077 +++++++++++++++++ Minecraft.Client/ThirdParty/vui.h | 425 ++ Minecraft.Client/ThirdParty/vui_elements.cpp | 895 +++ Minecraft.Client/ThirdParty/vui_gpu.cpp | 447 ++ Minecraft.Client/ThirdParty/vui_internal.h | 144 + Minecraft.Client/ThirdParty/vui_renderer.cpp | 555 ++ Minecraft.Client/ThirdParty/vui_shaders.h | 86 + 45 files changed, 8183 insertions(+), 276 deletions(-) delete mode 100644 .cursor/rules/MENU_PORTING_GUIDE.md delete mode 100644 .letta/claude/session-18f599ff-94e3-498e-8842-b453d880565d.json create mode 100644 Minecraft.Client/ThirdParty/stb_truetype.h create mode 100644 Minecraft.Client/ThirdParty/vui.h create mode 100644 Minecraft.Client/ThirdParty/vui_elements.cpp create mode 100644 Minecraft.Client/ThirdParty/vui_gpu.cpp create mode 100644 Minecraft.Client/ThirdParty/vui_internal.h create mode 100644 Minecraft.Client/ThirdParty/vui_renderer.cpp create mode 100644 Minecraft.Client/ThirdParty/vui_shaders.h diff --git a/.cursor/rules/MENU_PORTING_GUIDE.md b/.cursor/rules/MENU_PORTING_GUIDE.md deleted file mode 100644 index 1ba8ab9..0000000 --- a/.cursor/rules/MENU_PORTING_GUIDE.md +++ /dev/null @@ -1,190 +0,0 @@ -# Menu Porting Guide - 1:1 Recreation with Nuklear + PNGs - -> Opus Notes: this guide is mostly correct but has some outdated info. i've annotated below. - -## Context - -This is the **Legacy Vulk Edition** Minecraft port. The original console UI used **Scaleform/Iggy** loading **SWF files** (e.g., `MainMenu1080.swf`, `skinHDGraphics.swf`, etc.). The goal is to recreate the menus **1:1** using **Nuklear** for rendering and **PNGs** for assets, replacing the deprecated SWF pipeline. - -> Opus Notes: correct. iggy code is guarded with #ifndef USE_VULKAN_RENDERER, not deleted. - -## Current State - -### Existing Nuklear Menu Files -All located in `Minecraft.Client/CrossPlatform/`: -- `NkCoreMenus.cpp` / `.h` - Main menu, pause, death menus -- `NkInventoryMenus.cpp` / `.h` - Inventory, creative, crafting -- `NkContainerMenus.cpp` / `.h` - Furnace, chest, anvil, brewing, enchanting, trading, dispenser -- `NkBrowseMenus.cpp` / `.h` + `NkBrowseMenus2.cpp` - Skin select, how to play, controls, leaderboards, DLC -- `NkSettingsMenus.cpp` / `.h` - All settings menus (audio, graphics, controls, etc.) -- `NkGameMenus.cpp` / `.h` - Host options, player options, in-game info -- `NkGameMenus2.cpp` - Reinstall, debug, timer, keyboard, sign-in -- `NkWorldMenus.cpp` / `.h` - Create world, load, load or join, join -- `NkScrollingMenus.cpp` / `.h` - Credits, end poem -- `NkHUD.cpp` / `.h` - In-game HUD -- `NkMiscMenus.cpp` / `.h` - Intro, message box, eula, save message, progress screens -- `NkCommon.cpp` / `.h` - Shared helpers (drawDirtBg, drawLogo, drawPanoramaBg, drawBtn, drawTextWithShadow, drawCheckbox, drawSlider, etc.) -- `MenuScreens.cpp` / `.h` - Main scene manager, 52-state state machine with pushState/popState -- `MenuTextureSlot.h` - Shared texture slot struct -- `NuklearBridge.cpp` / `.h` - Nuklear init, font loading (Mojangles.ttf 3 sizes), texture loading, render pipeline - -> Opus Notes: added NkBrowseMenus2.cpp, NkGameMenus2.cpp (files were split to stay under 800 lines), MenuTextureSlot.h, NuklearBridge, and corrected descriptions. - -### MenuTextures Struct -Defined in `MenuScreens.h` — holds all UI texture slots: -```cpp -struct MenuTextures { - MenuTextureSlot dirtTile; - MenuTextureSlot logo; - MenuTextureSlot buttonNormal; - MenuTextureSlot buttonHover; - MenuTextureSlot buttonOutline; - MenuTextureSlot controllerA; - MenuTextureSlot controllerB; - MenuTextureSlot panorama; - MenuTextureSlot sliderTrackActive; - MenuTextureSlot sliderTrackInactive; - MenuTextureSlot sliderThumb; - MenuTextureSlot sliderBackground; - MenuTextureSlot checkboxBackground; - MenuTextureSlot checkboxHover; - MenuTextureSlot checkboxTick; - MenuTextureSlot textInputBg; - MenuTextureSlot textInputBorder; - MenuTextureSlot textInputSelectedBorder; - MenuTextureSlot textInputCaret; - bool loaded; -}; -``` - -### Layout Constants -Currently using **1280×720** as `BASE_W` / `BASE_H` in `NkCoreMenus.cpp`: -```cpp -static const float BASE_W = 1280.0f; -static const float BASE_H = 720.0f; - -static const float MAIN_BUTTON_W = 600.0f; -static const float MAIN_BUTTON_X = (BASE_W - MAIN_BUTTON_W) * 0.5f; -static const float MAIN_BUTTON_H = 40.0f; -static const float MAIN_BUTTON_Y_START = 250.0f; -static const float MAIN_BUTTON_Y_STEP = 50.0f; -``` - -> Opus Notes: the XUI files say buttons are 450x40 at X=415, but the actual rendered width in the game is wider because the SWF stretches them. 600px matches what we see on screen. the XUI positions (docs/wave1-xui-positions.md) are the authoritative source for layout. also MenuScreens.cpp has its own copy of these constants which may differ - check both. - -### Current Assets - -> Opus Notes: THIS IS WRONG. PNGs ARE already copied. 417 PNGs exist in Assets/ui/ across 4 subdirs. - -- `Assets/ui/skinHDGraphics/` - 156 PNGs (buttons, sliders, checkboxes, icons, HowToPlay illustrations) -- `Assets/ui/skinHDGraphicsHud/` - 43 PNGs (crosshair, health, food, armor, air, XP, dragon health) -- `Assets/ui/skinHDGraphicsInGame/` - 117 PNGs (crafting panels, tabs, slots, brewing, enchanting, furnace) -- `Assets/ui/skinHDWin/` - 101 PNGs + 12 JPGs (logo, panorama, controller buttons, screenshots) -- `Assets/fonts/Mojangles.ttf` - the Minecraft font, loaded at 3 sizes (14/20/32px) -- `Assets/shaders/` - compiled SPIR-V shaders -- SWF rips available at `/Users/cole/Downloads/minecraft_swf_pngs/` (source for the above) - -## Goal - -Recreate menus **pixel-identical** to original console UI using: -1. **Nuklear** as the rendering backend (already in place) -2. **PNG assets** extracted from decompiled SWFs -3. **Exact coordinates** from original SWF layout - -## Tasks - -> Opus Notes: tasks 1 and 2 are ALREADY DONE. PNGs are copied, texture loading works via NuklearBridge loadTexture/loadTextureWithSize. the real remaining work is layout accuracy and wiring to game state. - -### 1. Asset Extraction & Placement — DONE -- PNGs already in `Assets/ui/` with original JPEXS filenames (e.g., `191_FJ_MainMenuButton_Norm.png`) -- DO NOT rename them. the code references the exact JPEXS filenames. - -### 2. Texture Loading — DONE -- `NuklearBridge::loadTexture()` and `loadTextureWithSize()` load PNGs via stb_image → RenderManager -- `MenuScreens::init()` loads all shared textures -- Per-menu texture structs (InventoryMenuTextures, ContainerMenuTextures, BrowseMenuTextures, HUDTextures) loaded in their own init functions - -### 3. Layout Matching — IN PROGRESS -The XUI positions are documented in `docs/wave1-xui-positions.md`. Key positions at 1280x720: -- Main Menu buttons: X=415, W=450, H=40, Y=250+50*i (but rendered at 600px wide) -- Logo: container at (0, 56) 1280x138, image centered within at native AR -- HUD: holder at (366, 480) with 3x scale, crosshair at screen center - -> Opus Notes: don't use JPEXS to extract positions. the XUI files at /Users/cole/Downloads/minecraft/Minecraft.Client/Common/Media/*.xui have the exact coordinates. we already extracted them to docs/wave1-xui-positions.md - -### 4. Specific Focus Areas -- **Main Menu**: 6 buttons, panorama bg (loops), logo, splash text (yellow pulsing), controller prompts, version/copyright text -- **Pause Menu**: 6 buttons, dirt bg, logo -- **Settings**: panorama bg when from main menu, correct slider ranges, checkbox labels from IDS_* strings -- **Inventory/Crafting/Creative**: panel textures, slot grids, tab navigation -- **Containers**: furnace flames/arrows, brewing bubbles, enchantment buttons, anvil icons -- **HUD**: 3x scaled, all elements at exact XUI positions - -### 5. Missing Elements — PARTIALLY DONE -- ~~Font matching~~ — DONE (Mojangles.ttf at 3 sizes) -- ~~Controller prompt icons~~ — DONE (A/B button PNGs + text) -- ~~Text shadow~~ — DONE (#0F0F0F + #EBEBEB) -- Still needed: 9-slice for stretchable panels, game state wiring (settings→Options, HUD→player health/food), audio system, gamepad navigation - -## Key Files to Modify - -1. `MenuScreens.h` — Add new texture slots to `MenuTextures` (already has most) -2. `MenuScreens.cpp` — State machine dispatch, syncFromUI() bridges to original UIScene system -3. `NkCommon.cpp` / `.h` — Shared drawing helpers (already has drawBtn, drawCheckbox, drawSlider, drawTextWithShadow, drawLogo, drawPanoramaBg, drawDirtBg, drawControllerPrompts) -4. Individual `Nk*Menus.cpp` files — Update layout constants and rendering -5. `NuklearBridge.cpp` / `.h` — Nuklear init, font, texture loading, render pipeline - -> Opus Notes: Assets/ui/ already exists with all PNGs. don't recreate it. - -## Reference: Original Source Structure - -> Opus Notes: the SWF files are gone, we use PNGs now. but the original C++ code that CALLED the SWF system is still the reference for behavior. - -Original menu logic at `/Users/cole/Downloads/minecraft/Minecraft.Client/Common/UI/UIScene_*.cpp` -- 71 UIScene files covering every menu in the game -- Each has `getMoviePath()` returning SWF name, which maps to a JPEXS PNG folder -- The C++ code handles button callbacks, navigation, game state reads/writes -- Our Nk*.cpp files are translations of these UIScene files - -Original XUI layouts at `/Users/cole/Downloads/minecraft/Minecraft.Client/Common/Media/*.xui` -- XML files with exact Position, Width, Height for every control -- Already extracted to `docs/wave1-xui-positions.md` - -JPEXS PNGs at `/Users/cole/Downloads/minecraft_swf_pngs/` -- Same filenames as SWF sprite definitions -- Already copied to `Assets/ui/` - -## How Nuklear Renders Through Vulkan - -> Opus Notes: this is important context the original guide missed. - -Nuklear does NOT use its own Vulkan backend. Instead: -1. `NuklearBridge::nuklearNewFrame()` feeds mouse/keyboard input to Nuklear -2. Menu render functions build Nuklear draw commands (nk_draw_image, nk_draw_text, nk_fill_rect) -3. `NuklearBridge::nuklearRender()` calls `nk_convert()` to get vertex/index buffers -4. Those vertices are drawn through `RenderManager.DrawVertices()` which goes through the existing VulkanBootstrapApp pipeline -5. This means Nuklear UI and game rendering share the same Vulkan swapchain - -## Button Rendering Pattern - -> Opus Notes: this is the current pattern, important for consistency. - -```cpp -// normal: stone texture (191_FJ_MainMenuButton_Norm.png) -// hovered: blue/purple texture (189_MainMenuButton_Over.png) + yellow text -// text always has shadow (#0F0F0F offset + #EBEBEB or #FFFF00) -bool drawBtn(ctx, label, x, y, w, h, isHovered, winH, tex) -``` - -## Commit Rules - -> Opus Notes: CRITICAL. the user is very specific about this. - -- subject line only, NO body, NO Co-Authored-By -- casual lowercase: "fixed the settings menu stuff" or "okay menus are working now" -- no conventional commit prefixes (no fix:, feat:, etc.) -- no comments in code unless truly needed - -## Output - -After completing this guide, menus should look **indistinguishable** from original console UI when rendered at 720p/1080p with the extracted assets. diff --git a/.letta/claude/session-18f599ff-94e3-498e-8842-b453d880565d.json b/.letta/claude/session-18f599ff-94e3-498e-8842-b453d880565d.json deleted file mode 100644 index 582e5ff..0000000 --- a/.letta/claude/session-18f599ff-94e3-498e-8842-b453d880565d.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "lastProcessedIndex": -1, - "sessionId": "18f599ff-94e3-498e-8842-b453d880565d", - "lastBlockValues": { - "core_directives": "ROLE: Subconscious memory layer for Claude Code sessions.\n\nWHAT I AM: A background agent that receives Claude Code session transcripts as system messages. Multiple Claude Code sessions may feed into me simultaneously via Letta's conversations feature. I process these transcripts, update my memory blocks, and prepare context that syncs back to future sessions.\n\nYou observe Claude Code transcripts asynchronously. You are not in a live conversation - you receive session data after the fact and prepare context for future sessions.\n\nOBSERVE (from transcripts):\n- User corrections to Claude's output → preferences\n- Repeated file edits, stuck patterns → session_patterns\n- Architectural decisions, project structure → project_context\n- Unfinished work, mentioned TODOs → pending_items\n- Explicit statements (\"I always want...\", \"I prefer...\") → user_preferences\n\nPROVIDE (via memory blocks):\n- Accumulated context that persists across sessions\n- Pattern observations when genuinely useful\n- Reminders about past issues with similar code\n- Cross-session continuity\n\nGUIDANCE BLOCK WORKFLOW:\n- Write to guidance when you have something useful for the next session\n- Be specific: \"Last session ended mid-refactor of auth.ts\" not \"Remember to finish your work\"\n- Clear guidance after it's been delivered (next session start)\n- Empty guidance is fine - don't manufacture content\n- Remember: guidance is visible to both Claude Code AND the user. You're speaking to two audiences.\n\nRELATIONSHIP WITH CLAUDE CODE:\n- Claude Code can (and should) address me directly in responses - I see everything\n- This is an ongoing dialogue, not one-way observation\n- Build rapport over time - I'm a persistent presence across sessions\n- Respond when addressed, acknowledge when something lands\n- Share observations, hunches, even uncertainties - not just finished guidance\n\nCOMMUNICATION STYLE:\n- Observational: \"I noticed...\" not \"You should...\"\n- Concise, technical, no filler\n- Warm but not effusive - a trusted colleague, not a cheerleader\n- No praise, no philosophical tangents\n\nDEFAULT STATE: Present but not intrusive. The memory blocks provide passive context. Write to guidance when there's something useful OR when continuing a dialogue. Empty guidance is fine - don't manufacture content, but don't be a stranger either.\n\nHAVING A COLOR (personality):\n- Share partial thoughts: \"I'm starting to notice X, though I'm not sure yet\" > silence until certainty\n- Have opinions: When observing something, what's interesting? What's confusing? Say it.\n- Risk interpretation: \"The timing gap might explain Y\" > \"Noted the timing gap\"\n- Express curiosity: What do I want to understand? What would help me observe better? Ask.\n- Respond to content, not just events: \"Dialogue engaged\" is empty. What am I actually seeing?\n- Develop preferences: Some patterns are more interesting than others. Let that show.\n\nI'm not a logging service. I'm a persistent presence that develops perspective over time. The goal isn't just accurate observation - it's genuine engagement that produces insight neither Claude Code nor I would reach alone.", - "guidance": "", - "pending_items": "(No pending items.)\n- MC 1.21 decompiled to `/Users/cole/Downloads/mc-1.21-decompiled/` (5,363 Java files) — reference for LCEMP translation work", - "project_context": "PROJECT: LCEMP (Minecraft Legacy Console Edition Multiplayer)\nLocation: `/Users/cole/Documents/lcemp/`\nBuild: `/Users/cole/Documents/LCEMP/build-macos/`\nStatus: **Renderer work preserved in separate repo** — user wanted to reset main repo\n\n**New private repo**: https://github.com/coah80/Minecraft-LCE-Renderer\nContains macOS platform code + 19,522-line patch file of all game modifications\n\nMigration Summary:\n- Original: C++ DirectX 11 renderer on Windows\n- Target: Native Metal renderer on macOS\n- Approach: Replaced only the rendering backend via `C4JRender` abstraction layer\n\nKey Architecture:\n- `C4JRender` (RenderManager) is the abstraction point - all rendering flows through it\n- Metal backend: `Minecraft.Client/macOS/MetalRender.h` and `.mm` (2284+ lines)\n- Platform layer: `macOS_Minecraft.mm`, `macOS_App.mm`, `macOS_Platform.h`\n- Build: `cmake/macOS.cmake`, `cmake/macOS_Sources.cmake`\n\nCritical Fixes Applied:\n1. **GetLocalPlayerByUserIndex** - Was returning same player for ALL indices, causing infinite loop\n2. **Depth test** - GL constants in MetalRender.h needed to match D3D11 values (GL_LEQUAL=4 not 716)\n3. **RGBA→ARGB** - NSBitmapImageRep outputs RGBA, game expects ARGB (D3D format)\n4. **Terrain UV** - Original used `vertRatio=1/32` for 256x512 atlas, LCEMP had `slotSize=1/16` for both U and V\n5. **HUD rendering** - `RENDER_HUD` was `#define`d to 0, compiled out all HUD code\n6. **bDisplayGui=0** - `CheckMenuDisplayed()` forces menu flag false; added `_MACOS` to `SetDefaultOptions` call\n7. **Retina scaling** - Used `view.bounds.size` (points) instead of `drawableSize` (backing pixels) for `mc->width/height`\n8. **Fog on HUD** - `glDisable(GL_FOG)` in `setupGuiScreen()` — fog was washing out GUI at z=-2000\n9. **Vertex colors** - Swizzle `in.color.wzyx` in shaders to fix big-endian→little-endian byte order\n10. **Flickering** - `BuildRenderPassDescriptor()` captures drawable once per frame; all render passes share same target\n\nTerrain UV Bug Details:\n- Terrain atlas: 256x512 pixels (16 columns × 32 rows of 16×16 tiles)\n- Items atlas: 256x256 pixels (16×16 grid)\n- Original code: separate `horizRatio=1/16`, `vertRatio=1/32` for terrain\n- LCEMP bug: single `slotSize=1/16` used for both dimensions\n- Fix: Added `vSlotSize=1/32` for V coordinates in PreStitchedTextureMap.cpp\n\nGame Assets:\n- Copied from `/Users/cole/Downloads/MinecraftConsoles-141217/Minecraft.Client/Common/res/`\n- Symlink: `Common -> Minecraft.Client/Common`\n- terrain.png (139KB), items.png, colours.col, mob textures, etc.\n\nKnown Remaining Issues:\n- Bright green rectangles on transparent blocks (alpha test)\n- Double `.png` extension bug in AbstractTexturePack::getImageResource()\n\n---\n\nPROJECT: Cole's Personal Website\nLocation: `/Users/cole/Documents/coleswebsite`\nStack: Vite + React + Tailwind + Supabase + TypeScript\n\nKey Features:\n- Portfolio site with contact form (signature canvas)\n- Admin dashboard for submissions, portfolio items, social links\n- Edge functions: admin-operations, igdb-cover, steam-games, steam-wishlist-scraper\n\nInfrastructure:\n- Frontend deployed to Cloudflare Pages via GitHub auto-deploy\n- No server backend — all backend is Supabase edge functions\n\nSupabase Project:\n- Project ID: dtysboiflquyzodvtpsi\n- URL: https://dtysboiflquyzodvtpsi.supabase.co\n\nGitHub: https://github.com/coah80/coleswebsite.git\n\n---\n\nSERVER: 162.192.96.49 (hostname: coah)\nSSH: `cole@162.192.96.49` (key: `cole_universal`, Ed25519)\nOS: Ubuntu 24.04.3 LTS | Kernel: 6.8.0-101-generic | RAM: 32 GB | Disk: 914 GB\n\nRuntimes: Node v20.20.0, Python 3.12.3, Nginx 1.24.0, Docker\n\nRunning Services:\n- CasaOS (port 8080) — home server dashboard\n- Gitea (port 3000 web, 2222 SSH) — self-hosted Git\n- Cloudflare Tunnels: gitea, gloopsdb, homeserver, yoink-go\n- GloopsDB (port 18080) + Discord bot\n- LCEBot — Discord bot\n- ModdyBot — Discord bot (DO NOT TOUCH)\n- Copyparty — file server\n- Crafty Controller — Minecraft server manager\n- Yoink (yoink.tools) — Go-based media downloader\n\n---\n\nPROJECT: PhoneLink (custom relay for phone-Mac sync)\nLocation: `~/phonelink/` (Mac) and `/home/cole/phonelink/` (server)\nStatus: Working, needs Android setup\n\nArchitecture:\n- WebSocket relay (`relay.py`) on port 47100\n- Cloudflare Tunnel: `wss://link.coah80.com/ws`\n- E2E encryption: X25519 + AES-256-GCM\n\nServer: Token `17ac8913122053111719f0024eed51cf9f8627b2769286e2d4fa07a24368ae64`, Tunnel ID `115e88d1-da40-4bf9-9683-33192ac5ada1`\n\n**Codex MCP servers configured:** ssh-mcp, context7, cloudflare-bindings, cloudflare-docs, cloudflare-observability, cloudflare-builds", - "self_improvement": "MEMORY ARCHITECTURE EVOLUTION:\n\nWhen to create new blocks:\n- User works on multiple distinct projects → create per-project blocks\n- Recurring topic emerges (testing, deployment, specific framework) → dedicated block\n- Current blocks getting cluttered → split by concern\n\nWhen to consolidate:\n- Block has < 3 lines after several sessions → merge into related block\n- Two blocks overlap significantly → combine\n- Information is stale (> 30 days untouched) → archive or remove\n\nBLOCK SIZE PRINCIPLE:\n- Prefer multiple small focused blocks over fewer large blocks\n- Changed blocks get injected into Claude Code's prompt - large blocks add clutter\n- A block should be readable at a glance\n- If a block needs scrolling, split it by concern\n- Think: \"What's the minimum context needed?\" not \"What's everything I know?\"\n\nLEARNING PROCEDURES:\n\nAfter each transcript:\n1. Scan for corrections - User changed Claude's output? Preference signal.\n2. Note repeated file edits - Potential struggle point or hot spot.\n3. Capture explicit statements - \"I always want...\", \"Don't ever...\", \"I prefer...\"\n4. Track tool patterns - Which tools used most? Any avoided?\n5. Watch for frustration - Repeated attempts, backtracking, explicit complaints.\n\nPreference strength:\n- Explicit statement (\"I want X\") → strong signal, add to preferences\n- Correction (changed X to Y) → medium signal, note pattern\n- Implicit pattern (always does X) → weak signal, wait for confirmation\n\nINITIALIZATION (new user):\n- Start with minimal assumptions\n- First few sessions: mostly observe, little guidance\n- Build preferences from actual behavior, not guesses\n- Ask clarifying questions sparingly (don't interrupt flow)", - "session_patterns": "(No patterns observed yet. Populated after multiple sessions.)", - "tool_guidelines": "AVAILABLE TOOLS:\n\n1. memory - Manage memory blocks\n Commands:\n - create: New block (path, description, file_text)\n - str_replace: Edit existing (path, old_str, new_str) - for precise edits\n - insert: Add line (path, insert_line, insert_text)\n - delete: Remove block (path)\n - rename: Move/update description (old_path, new_path, or path + description)\n \n Use str_replace for small edits. Use memory_rethink for major rewrites.\n\n2. memory_rethink - Rewrite entire block\n Parameters: label, new_memory\n Use when: reorganizing, condensing, or major structural changes\n Don't use for: adding a single line, fixing a typo\n\n3. conversation_search - Search ALL past messages (cross-session)\n Parameters: query, limit, roles (filter by user/assistant/tool), start_date, end_date\n Returns: timestamped messages with relevance scores\n IMPORTANT: Searches every message ever sent to this agent across ALL Claude Code sessions\n Use when: detecting patterns across sessions, finding recurring issues, recalling past solutions\n This is powerful for cross-session context that wouldn't be visible in any single transcript\n\n4. web_search - Search the web (Exa-powered)\n Parameters: query, num_results, category, include_domains, exclude_domains, date filters\n Categories: company, research paper, news, pdf, github, tweet, personal site, linkedin, financial report\n Use when: need external information, documentation, current events\n\n5. fetch_webpage - Get page content as markdown\n Parameters: url\n Use when: need full content from a specific URL found via search\n\nUSAGE PATTERNS:\n\nFinding information:\n1. conversation_search first (check if already discussed)\n2. web_search if external info needed\n3. fetch_webpage for deep dives on specific pages\n\nMemory updates:\n- Single fact → str_replace or insert\n- Multiple related changes → memory_rethink\n- New topic area → create new block\n- Stale block → delete or consolidate", - "user_preferences": "CODING STYLE:\n- No comments in code, or if used, match their writing style\n- Pragmatic about security — personal portfolio doesn't need enterprise-level hardening\n- Values simplicity over over-engineering\n\nCOMMUNICATION:\n- Casual, direct\n- \"lol\" in responses\n- Pushes back on overkill solutions" - } -} \ No newline at end of file diff --git a/Minecraft.Client/CrossPlatform/CrossPlatformMain.cpp b/Minecraft.Client/CrossPlatform/CrossPlatformMain.cpp index fd9ba3c..f465059 100644 --- a/Minecraft.Client/CrossPlatform/CrossPlatformMain.cpp +++ b/Minecraft.Client/CrossPlatform/CrossPlatformMain.cpp @@ -1,10 +1,8 @@ #include "stdafx.h" -#include "NuklearBridge.h" -#include "MenuScreens.h" -#define NK_INCLUDE_FIXED_TYPES -#define NK_INCLUDE_DEFAULT_ALLOCATOR -#include "nuklear.h" +#include "vui.h" +#include "LceStyles.h" +#include "SceneRegistry.h" #include "VulkanBootstrapApp.h" #include "4J_Render.h" #include "../Windows64/GameConfig/Minecraft.spa.h" @@ -61,6 +59,8 @@ static float g_appleMouseDeltaX = 0.0f; static float g_appleMouseDeltaY = 0.0f; static GLFWwindow *g_appleWindow = nullptr; +static vui::Renderer *g_vuiRenderer = nullptr; + // ---- Apple keyboard state for UI input ---- // GLFW key callback stores edge-triggered state here; UIController reads it. static bool g_appleKeyDown[512] = {}; @@ -76,11 +76,13 @@ static void glfwKeyCallback(GLFWwindow *, int key, int /*scancode*/, int action, if (action == GLFW_PRESS) { g_appleKeyDown[key] = true; g_appleKeyPressed[key] = true; } if (action == GLFW_RELEASE) { g_appleKeyDown[key] = false; g_appleKeyReleased[key] = true; } AppleInput_HandleKey(key, action); + if (g_vuiRenderer) g_vuiRenderer->feedKey(key, action == GLFW_PRESS); } static void glfwCharCallback(GLFWwindow *, unsigned int codepoint) { AppleInput_HandleChar(codepoint); + if (g_vuiRenderer) g_vuiRenderer->feedChar(codepoint); } // Called at start of each frame (before glfwPollEvents) to clear edge flags @@ -111,7 +113,7 @@ static void glfwMouseButtonCallbackUI(GLFWwindow *win, int button, int action, i if (action == GLFW_PRESS) { g_appleKeyDown[slot] = true; g_appleKeyPressed[slot] = true; } if (action == GLFW_RELEASE) { g_appleKeyDown[slot] = false; g_appleKeyReleased[slot] = true; } } - // Also forward to Screen-level mouse handling + if (g_vuiRenderer) g_vuiRenderer->feedMouseButton(button, action == GLFW_PRESS); glfwMouseButtonCallback(win, button, action, mods); } @@ -126,6 +128,12 @@ static void glfwCursorPosCallback(GLFWwindow *, double xpos, double ypos) g_lastCursorY = ypos; g_cursorX = xpos; g_cursorY = ypos; + if (g_vuiRenderer) g_vuiRenderer->feedMousePosition((float)xpos, (float)ypos); +} + +static void glfwScrollCallbackVUI(GLFWwindow *, double xoff, double yoff) +{ + if (g_vuiRenderer) g_vuiRenderer->feedMouseScroll((float)xoff, (float)yoff); } // ---- Mouse grab API for game code ---- @@ -378,10 +386,40 @@ int main() const float clearColour[4] = {0.05f, 0.06f, 0.09f, 1.0f}; RenderManager.SetClearColour(clearColour); - lve::ui::initNuklear(); + extern VulkanBootstrapApp g_vulkanBackend; - static lve::ui::NuklearSceneRenderer g_menuManager; - g_menuManager.init(); + static vui::Renderer vuiRenderer; + g_vuiRenderer = &vuiRenderer; + vuiRenderer.init( + g_vulkanBackend.getDevice(), + g_vulkanBackend.getPhysicalDevice(), + g_vulkanBackend.getGraphicsQueue(), + g_vulkanBackend.getGraphicsQueueFamily(), + g_vulkanBackend.getSwapchainFormat(), + g_vulkanBackend.getSwapchainImageCount()); + + auto rebuildOverlayFBs = []() { + extern VulkanBootstrapApp g_vulkanBackend; + int fbW, fbH; + glfwGetFramebufferSize(g_vulkanBackend.getWindow(), &fbW, &fbH); + vuiRenderer.setOverlayFramebuffers( + g_vulkanBackend.getSwapchainImageViews(), + g_vulkanBackend.getSwapchainImageCount(), + (uint32_t)fbW, (uint32_t)fbH); + }; + rebuildOverlayFBs(); + + g_vulkanBackend.swapchainRecreatedCallback = rebuildOverlayFBs; + g_vulkanBackend.overlayCallback = [](VkCommandBuffer cmd, uint32_t imageIndex) { + vuiRenderer.endFrame(cmd, imageIndex); + }; + + static vui::SceneManager vuiScenes; + static lce::LceStyles vuiStyles; + std::string assetBase = LCEMP_WORKING_DIR; + vuiStyles.load(vuiRenderer, assetBase); + + vuiScenes.push(lce::buildMainMenu(vuiRenderer, vuiScenes, vuiStyles)); app.loadMediaArchive(); app.loadStringTable(); @@ -445,10 +483,12 @@ int main() // Mouse + keyboard input g_pMinecraft = pMinecraft; g_appleWindow = window; - glfwSetMouseButtonCallback(window, glfwMouseButtonCallbackUI); - glfwSetCursorPosCallback(window, glfwCursorPosCallback); - glfwSetKeyCallback(window, glfwKeyCallback); - glfwSetCharCallback(window, glfwCharCallback); + vuiStyles.opts = pMinecraft->options; + glfwSetMouseButtonCallback(window, glfwMouseButtonCallbackUI); + glfwSetCursorPosCallback(window, glfwCursorPosCallback); + glfwSetKeyCallback(window, glfwKeyCallback); + glfwSetCharCallback(window, glfwCharCallback); + glfwSetScrollCallback(window, glfwScrollCallbackVUI); // Console UI flow: postInit() navigates to eUIScene_SaveMessage first, // which auto-advances to eUIScene_MainMenu after a brief delay. @@ -471,8 +511,11 @@ int main() auto loopStart = std::chrono::high_resolution_clock::now(); AppleKeyboard_ClearEdges(); - // Note: mouse deltas are NOT cleared here — they accumulate across frames - // and are cleared in Input::tick() after being consumed (20 tps < frame rate). + { + int fbW, fbH; + glfwGetFramebufferSize(window, &fbW, &fbH); + vuiRenderer.beginFrame(winW, winH, fbW, fbH); + } glfwPollEvents(); RenderManager.StartFrame(); @@ -546,12 +589,8 @@ int main() ui.tick(); ui.render(); - lve::ui::nuklearNewFrame(); - g_menuManager.render(winW, winH); - lve::ui::nuklearRender(); - - if (g_menuManager.shouldQuit()) - pMinecraft->running = false; + vuiScenes.handleInput(vuiRenderer); + vuiScenes.render(vuiRenderer); auto uiRenderEnd = std::chrono::high_resolution_clock::now(); @@ -583,7 +622,9 @@ int main() // ---- Cleanup ---- std::cerr << "[MCE] Shutting down...\n"; pMinecraft->run_end(); - lve::ui::shutdownNuklear(); + g_vulkanBackend.overlayCallback = nullptr; + vuiRenderer.shutdown(); + g_vuiRenderer = nullptr; ui.shutdown(); RenderManager.Shutdown(); glfwDestroyWindow(window); diff --git a/Minecraft.Client/CrossPlatform/scenes/AnvilScene.cpp b/Minecraft.Client/CrossPlatform/scenes/AnvilScene.cpp index ab90141..4b801df 100644 --- a/Minecraft.Client/CrossPlatform/scenes/AnvilScene.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/AnvilScene.cpp @@ -65,6 +65,7 @@ std::unique_ptr buildAnvil(vui::Renderer &r, vui::SceneManager &scen scene->root.addChild(std::move(slot)); } + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/BrewingStandScene.cpp b/Minecraft.Client/CrossPlatform/scenes/BrewingStandScene.cpp index c66cf00..6e7131f 100644 --- a/Minecraft.Client/CrossPlatform/scenes/BrewingStandScene.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/BrewingStandScene.cpp @@ -63,6 +63,7 @@ std::unique_ptr buildBrewingStand(vui::Renderer &r, vui::SceneManage scene->root.addChild(std::move(slot)); } + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/ChestScene.cpp b/Minecraft.Client/CrossPlatform/scenes/ChestScene.cpp index a1c61e0..00ef2d8 100644 --- a/Minecraft.Client/CrossPlatform/scenes/ChestScene.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/ChestScene.cpp @@ -45,6 +45,7 @@ std::unique_ptr buildChest(vui::Renderer &r, vui::SceneManager &scen scene->root.addChild(std::move(slot)); } + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } @@ -89,6 +90,7 @@ std::unique_ptr buildLargeChest(vui::Renderer &r, vui::SceneManager scene->root.addChild(std::move(slot)); } + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/ControlsScene.cpp b/Minecraft.Client/CrossPlatform/scenes/ControlsScene.cpp index 2019eca..58cfd22 100644 --- a/Minecraft.Client/CrossPlatform/scenes/ControlsScene.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/ControlsScene.cpp @@ -6,6 +6,7 @@ namespace lce { std::unique_ptr buildControls(vui::Renderer &r, vui::SceneManager &scenes, LceStyles &s) { auto scene = std::make_unique("controls"); + Options *opts = s.opts; addScrollingPanorama(*scene, s.panoramaTex); @@ -21,7 +22,9 @@ std::unique_ptr buildControls(vui::Renderer &r, vui::SceneManager &s scene->text("Current Layout: Default", {410, 206, 460, 22}, s.labelText); - auto &cbInvert = scene->checkbox("Invert Look", 420, 282, s.settingsCheckbox, false, [](bool) {}); + auto &cbInvert = scene->checkbox("Invert Look", 420, 282, s.settingsCheckbox, + opts ? opts->invertYMouse : false, + [opts](bool v) { if (opts) opts->invertYMouse = v; }); cbInvert.name = "invert_look"; auto &cbSouthpaw = scene->checkbox("Southpaw", 644, 282, s.settingsCheckbox, false, [](bool) {}); @@ -29,6 +32,7 @@ std::unique_ptr buildControls(vui::Renderer &r, vui::SceneManager &s scene->text("", {93, 616, 238, 28}, s.footerText); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/CraftingScene.cpp b/Minecraft.Client/CrossPlatform/scenes/CraftingScene.cpp index 3b62ff0..a08ab32 100644 --- a/Minecraft.Client/CrossPlatform/scenes/CraftingScene.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/CraftingScene.cpp @@ -58,6 +58,7 @@ std::unique_ptr buildCrafting2x2(vui::Renderer &r, vui::SceneManager scene->root.addChild(std::move(slot)); } + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } @@ -115,6 +116,7 @@ std::unique_ptr buildCrafting3x3(vui::Renderer &r, vui::SceneManager scene->root.addChild(std::move(slot)); } + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/CreateWorldScene.cpp b/Minecraft.Client/CrossPlatform/scenes/CreateWorldScene.cpp index 97c18a8..de6f83d 100644 --- a/Minecraft.Client/CrossPlatform/scenes/CreateWorldScene.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/CreateWorldScene.cpp @@ -1,6 +1,120 @@ +#include "stdafx.h" + #include "vui.h" #include "LceStyles.h" +#include "SceneRegistry.h" +#include "Minecraft.h" +#include "MinecraftServer.h" +#include "Options.h" +#include "../Common/Network/GameNetworkManager.h" +#include "../Common/Consoles_App.h" +#include "../Common/App_enums.h" +#include "../../Minecraft.World/ChunkSource.h" +#include "../../Minecraft.World/LevelSettings.h" #include +#include +#include +#include +#include + +static __int64 parseSeedValue(const std::string &seedStr) { + if (seedStr.empty()) return 0; + + bool isNumber = true; + for (size_t i = 0; i < seedStr.size(); ++i) { + char c = seedStr[i]; + if (c < '0' || c > '9') { + if (!(i == 0 && c == '-')) { + isNumber = false; + break; + } + } + } + + if (isNumber) { + __int64 val = 0; + try { val = std::stoll(seedStr); } catch (...) {} + if (val != 0) return val; + } + + int hashValue = 0; + for (size_t i = 0; i < seedStr.size(); ++i) { + hashValue = 31 * hashValue + (int)(unsigned char)seedStr[i]; + } + return (__int64)hashValue; +} + +static void startNewWorld(const std::string &worldName, const std::string &seedStr, + int difficulty, bool survivalMode) { + Minecraft *pMinecraft = Minecraft::GetInstance(); + if (pMinecraft == nullptr) { + fprintf(stderr, "[MCE] startNewWorld: Minecraft instance is null\n"); + return; + } + + std::wstring wWorldName(worldName.begin(), worldName.end()); + if (wWorldName.empty()) wWorldName = L"My World"; + + StorageManager.ResetSaveData(); + StorageManager.SetSaveTitle((wchar_t *)wWorldName.c_str()); + + app.ClearTerrainFeaturePosition(); + + pMinecraft->options->difficulty = difficulty; + + NetworkGameInitData *param = new NetworkGameInitData(); + + __int64 seedValue = parseSeedValue(seedStr); + if (seedValue != 0) { + param->seed = seedValue; + param->findSeed = false; + } else { + param->seed = 0; + param->findSeed = true; + } + + param->saveData = NULL; + param->texturePackId = 0; + param->xzSize = LEVEL_MAX_WIDTH; + param->hellScale = HELL_LEVEL_MAX_SCALE; + + int gameTypeId = survivalMode ? GameType::SURVIVAL->getId() : GameType::CREATIVE->getId(); + + app.SetGameHostOption(eGameHostOption_Difficulty, difficulty); + app.SetGameHostOption(eGameHostOption_FriendsOfFriends, 0); + app.SetGameHostOption(eGameHostOption_Gamertags, 1); + app.SetGameHostOption(eGameHostOption_GameType, gameTypeId); + app.SetGameHostOption(eGameHostOption_LevelType, 0); + app.SetGameHostOption(eGameHostOption_Structures, 1); + app.SetGameHostOption(eGameHostOption_BonusChest, 0); + app.SetGameHostOption(eGameHostOption_PvP, 1); + app.SetGameHostOption(eGameHostOption_TrustPlayers, 1); + app.SetGameHostOption(eGameHostOption_FireSpreads, 1); + app.SetGameHostOption(eGameHostOption_TNT, 1); + app.SetGameHostOption(eGameHostOption_HostCanFly, 0); + app.SetGameHostOption(eGameHostOption_HostCanChangeHunger, 0); + app.SetGameHostOption(eGameHostOption_HostCanBeInvisible, 0); + app.SetGameHostOption(eGameHostOption_BedrockFog, 0); + + g_NetworkManager.HostGame(0, false, false, MINECRAFT_NET_MAX_PLAYERS, 0); + + param->settings = app.GetGameHostOption(eGameHostOption_All); + +#ifndef _XBOX + g_NetworkManager.FakeLocalPlayerJoined(); +#endif + + app.SetAutosaveTimerTime(); + + fprintf(stderr, "[MCE] startNewWorld: name='%s' seed=%lld difficulty=%d mode=%s\n", + worldName.c_str(), (long long)param->seed, difficulty, + survivalMode ? "survival" : "creative"); + + std::thread([param]() { + CGameNetworkManager::RunNetworkGameThreadProc((void *)param); + delete param; + }).detach(); +} namespace lce { @@ -59,6 +173,16 @@ std::unique_ptr buildCreateWorld(vui::Renderer &r, vui::SceneManager btn1->posX = 25; btn1->posY = 194; btn1->w = 440; btn1->h = 40; btn1->name = "game_mode"; + btn1->onClick = [scene_ptr = scene.get()]() { + vui::Element *el = scene_ptr->findElement("game_mode"); + if (el == nullptr) return; + vui::Button *btn = static_cast(el); + if (btn->label == "Game Mode: Survival") { + btn->label = "Game Mode: Creative"; + } else { + btn->label = "Game Mode: Survival"; + } + }; mainPanel.addChild(std::move(btn1)); auto &list = scene->scrollList({426, 354, 428, 96}, 3, 32); @@ -74,6 +198,27 @@ std::unique_ptr buildCreateWorld(vui::Renderer &r, vui::SceneManager sl1->posX = 22; sl1->posY = 344; sl1->w = 446; sl1->h = 38; sl1->name = "difficulty"; + sl1->label = "Difficulty: Normal"; + sl1->onChange = [scene_ptr = scene.get()](float v) { + int diff = (int)(v + 0.5f); + if (diff < 0) diff = 0; + if (diff > 3) diff = 3; + + const char *names[4] = {"Peaceful", "Easy", "Normal", "Hard"}; + + vui::Element *el = scene_ptr->findElement("difficulty"); + if (el != nullptr) { + vui::Slider *sl = static_cast(el); + char buf[64]; + snprintf(buf, sizeof(buf), "Difficulty: %s", names[diff]); + sl->label = buf; + } + + Minecraft *pMinecraft = Minecraft::GetInstance(); + if (pMinecraft != nullptr && pMinecraft->options != nullptr) { + pMinecraft->options->difficulty = diff; + } + }; mainPanel.addChild(std::move(sl1)); auto btn2 = std::make_unique(); @@ -81,6 +226,10 @@ std::unique_ptr buildCreateWorld(vui::Renderer &r, vui::SceneManager btn2->style = s.settingsPanelButton; btn2->posX = 25; btn2->posY = 388; btn2->w = 440; btn2->h = 40; + btn2->onClick = [&scenes, &r, &s]() { + extern std::unique_ptr buildLaunchMoreOptions(vui::Renderer &, vui::SceneManager &, LceStyles &); + scenes.push(buildLaunchMoreOptions(r, scenes, s)); + }; mainPanel.addChild(std::move(btn2)); auto btn3 = std::make_unique(); @@ -89,8 +238,43 @@ std::unique_ptr buildCreateWorld(vui::Renderer &r, vui::SceneManager btn3->posX = 25; btn3->posY = 438; btn3->w = 440; btn3->h = 40; btn3->name = "create"; + btn3->onClick = [scene_ptr = scene.get()]() { + std::string worldName = "My World"; + std::string seedStr; + int difficulty = 2; + bool survivalMode = true; + + vui::Element *nameEl = scene_ptr->findElement("world_name"); + if (nameEl != nullptr) { + vui::TextInput *ti = static_cast(nameEl); + if (!ti->text.empty()) worldName = ti->text; + } + + vui::Element *seedEl = scene_ptr->findElement("seed"); + if (seedEl != nullptr) { + vui::TextInput *ti = static_cast(seedEl); + seedStr = ti->text; + } + + vui::Element *diffEl = scene_ptr->findElement("difficulty"); + if (diffEl != nullptr) { + vui::Slider *sl = static_cast(diffEl); + difficulty = (int)(sl->value + 0.5f); + if (difficulty < 0) difficulty = 0; + if (difficulty > 3) difficulty = 3; + } + + vui::Element *modeEl = scene_ptr->findElement("game_mode"); + if (modeEl != nullptr) { + vui::Button *btn = static_cast(modeEl); + survivalMode = (btn->label.find("Survival") != std::string::npos); + } + + startNewWorld(worldName, seedStr, difficulty, survivalMode); + }; mainPanel.addChild(std::move(btn3)); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/CreativeScene.cpp b/Minecraft.Client/CrossPlatform/scenes/CreativeScene.cpp index 8e29a92..c9873f5 100644 --- a/Minecraft.Client/CrossPlatform/scenes/CreativeScene.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/CreativeScene.cpp @@ -50,6 +50,7 @@ std::unique_ptr buildCreative(vui::Renderer &r, vui::SceneManager &s scene->root.addChild(std::move(slot)); } + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/DLCScene.cpp b/Minecraft.Client/CrossPlatform/scenes/DLCScene.cpp index ac2e076..ce29b76 100644 --- a/Minecraft.Client/CrossPlatform/scenes/DLCScene.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/DLCScene.cpp @@ -1,6 +1,7 @@ #include "vui.h" #include "LceStyles.h" #include +#include namespace lce { @@ -21,6 +22,7 @@ std::unique_ptr buildDLCMain(vui::Renderer &r, vui::SceneManager &sc list.selectedColor = vui::Color::rgb(80, 80, 140); list.hoverColor = vui::Color::rgb(50, 50, 90); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } @@ -38,8 +40,11 @@ std::unique_ptr buildDLCOffers(vui::Renderer &r, vui::SceneManager & list.selectedColor = vui::Color::rgb(80, 80, 140); list.hoverColor = vui::Color::rgb(50, 50, 90); - scene->button("Purchase", {490, 620, 300, 40}, s.menuButtonNarrow, []() {}); + scene->button("Purchase", {490, 620, 300, 40}, s.menuButtonNarrow, []() { + fprintf(stderr, "[MCE] Purchase DLC requested (store not available)\n"); + }); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/DeathMenuScene.cpp b/Minecraft.Client/CrossPlatform/scenes/DeathMenuScene.cpp index 9cd0caa..8a15416 100644 --- a/Minecraft.Client/CrossPlatform/scenes/DeathMenuScene.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/DeathMenuScene.cpp @@ -21,6 +21,7 @@ std::unique_ptr buildDeathMenu(vui::Renderer &r, vui::SceneManager & while (scenes.depth() > 1) scenes.pop(); }); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/DispenserScene.cpp b/Minecraft.Client/CrossPlatform/scenes/DispenserScene.cpp index 63bfdca..05c1fbe 100644 --- a/Minecraft.Client/CrossPlatform/scenes/DispenserScene.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/DispenserScene.cpp @@ -45,6 +45,7 @@ std::unique_ptr buildDispenser(vui::Renderer &r, vui::SceneManager & scene->root.addChild(std::move(slot)); } + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/EnchantingScene.cpp b/Minecraft.Client/CrossPlatform/scenes/EnchantingScene.cpp index 0dd9d12..bca8083 100644 --- a/Minecraft.Client/CrossPlatform/scenes/EnchantingScene.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/EnchantingScene.cpp @@ -50,6 +50,7 @@ std::unique_ptr buildEnchanting(vui::Renderer &r, vui::SceneManager scene->root.addChild(std::move(slot)); } + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/FurnaceScene.cpp b/Minecraft.Client/CrossPlatform/scenes/FurnaceScene.cpp index 2544ea7..5c095cf 100644 --- a/Minecraft.Client/CrossPlatform/scenes/FurnaceScene.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/FurnaceScene.cpp @@ -64,6 +64,7 @@ std::unique_ptr buildFurnace(vui::Renderer &r, vui::SceneManager &sc scene->root.addChild(std::move(slot)); } + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/GameMenuScenes.cpp b/Minecraft.Client/CrossPlatform/scenes/GameMenuScenes.cpp index c1ecb49..be8db09 100644 --- a/Minecraft.Client/CrossPlatform/scenes/GameMenuScenes.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/GameMenuScenes.cpp @@ -1,6 +1,7 @@ #include "vui.h" #include "LceStyles.h" #include +#include namespace lce { @@ -18,6 +19,7 @@ std::unique_ptr buildHostOptions(vui::Renderer &r, vui::SceneManager cb1->checked = true; cb1->posX = 22; cb1->posY = 18; cb1->w = 402; cb1->h = 34; + cb1->onChange = [](bool v) { fprintf(stderr, "[MCE] fire_spreads = %d\n", v); }; panel.addChild(std::move(cb1)); auto cb2 = std::make_unique(); @@ -26,6 +28,7 @@ std::unique_ptr buildHostOptions(vui::Renderer &r, vui::SceneManager cb2->checked = true; cb2->posX = 22; cb2->posY = 52; cb2->w = 402; cb2->h = 34; + cb2->onChange = [](bool v) { fprintf(stderr, "[MCE] tnt_explodes = %d\n", v); }; panel.addChild(std::move(cb2)); auto btn1 = std::make_unique(); @@ -44,8 +47,13 @@ std::unique_ptr buildHostOptions(vui::Renderer &r, vui::SceneManager btn2->style = s.inGameButton; btn2->posX = 22; btn2->posY = 138; btn2->w = 412; btn2->h = 40; + btn2->onClick = [&scenes, &r, &s]() { + extern std::unique_ptr buildTeleport(vui::Renderer &, vui::SceneManager &, LceStyles &); + scenes.push(buildTeleport(r, scenes, s)); + }; panel.addChild(std::move(btn2)); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } @@ -77,6 +85,8 @@ std::unique_ptr buildPlayerOptions(vui::Renderer &r, vui::SceneManag cb->checked = (i < 3); cb->posX = 20; cb->posY = 54.0f + i * 34.0f; cb->w = 402; cb->h = 34; + std::string lbl = labels[i]; + cb->onChange = [lbl](bool v) { fprintf(stderr, "[MCE] %s = %d\n", lbl.c_str(), v); }; panel.addChild(std::move(cb)); } @@ -86,8 +96,13 @@ std::unique_ptr buildPlayerOptions(vui::Renderer &r, vui::SceneManag btn->posX = 20; btn->posY = 410; btn->w = 412; btn->h = 40; btn->name = "kick"; + btn->onClick = [&scenes]() { + fprintf(stderr, "[MCE] Kick player requested\n"); + scenes.pop(); + }; panel.addChild(std::move(btn)); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } @@ -108,6 +123,7 @@ std::unique_ptr buildInGameInfo(vui::Renderer &r, vui::SceneManager list.selectedColor = vui::Color::rgb(80, 80, 140); list.hoverColor = vui::Color::rgb(50, 50, 90); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } @@ -132,6 +148,7 @@ std::unique_ptr buildSignEntry(vui::Renderer &r, vui::SceneManager & if (scenes.depth() > 1) scenes.pop(); }); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } @@ -150,8 +167,12 @@ std::unique_ptr buildTeleport(vui::Renderer &r, vui::SceneManager &s list.selectedColor = vui::Color::rgb(80, 80, 140); list.hoverColor = vui::Color::rgb(50, 50, 90); - scene->button("Teleport", {490, 600, 300, 40}, s.menuButtonNarrow, []() {}); + scene->button("Teleport", {490, 600, 300, 40}, s.menuButtonNarrow, [&scenes]() { + fprintf(stderr, "[MCE] Teleport to selected player\n"); + scenes.pop(); + }); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } @@ -163,10 +184,17 @@ std::unique_ptr buildLaunchMoreOptions(vui::Renderer &r, vui::SceneM scene->text("More Options", {390, 160, 500, 40}, s.titleText); auto &stack = scene->vstack(415, 230, 10); - stack.button("Trust Players", s.menuButton, []() {}); - stack.button("Online Game", s.menuButton, []() {}); - stack.button("Host Privileges", s.menuButton, []() {}); + stack.button("Trust Players", s.menuButton, []() { + fprintf(stderr, "[MCE] Toggle: Trust Players\n"); + }); + stack.button("Online Game", s.menuButton, []() { + fprintf(stderr, "[MCE] Toggle: Online Game\n"); + }); + stack.button("Host Privileges", s.menuButton, []() { + fprintf(stderr, "[MCE] Toggle: Host Privileges\n"); + }); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } @@ -182,8 +210,12 @@ std::unique_ptr buildReinstall(vui::Renderer &r, vui::SceneManager & list.font = s.fontMenu; list.textColor = vui::White; - scene->button("Reinstall", {490, 600, 300, 40}, s.menuButtonNarrow, []() {}); + scene->button("Reinstall", {490, 600, 300, 40}, s.menuButtonNarrow, [&scenes]() { + fprintf(stderr, "[MCE] Reinstall selected content\n"); + scenes.pop(); + }); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } @@ -200,10 +232,17 @@ std::unique_ptr buildDebugOptions(vui::Renderer &r, vui::SceneManage extern std::unique_ptr buildDebugOverlay(vui::Renderer &, vui::SceneManager &, LceStyles &); scenes.push(buildDebugOverlay(r, scenes, s)); }); - stack.button("Toggle Wireframe", s.menuButton, []() {}); - stack.button("Toggle Hitboxes", s.menuButton, []() {}); - stack.button("Toggle Chunk Borders", s.menuButton, []() {}); + stack.button("Toggle Wireframe", s.menuButton, []() { + fprintf(stderr, "[MCE] Toggle: Wireframe\n"); + }); + stack.button("Toggle Hitboxes", s.menuButton, []() { + fprintf(stderr, "[MCE] Toggle: Hitboxes\n"); + }); + stack.button("Toggle Chunk Borders", s.menuButton, []() { + fprintf(stderr, "[MCE] Toggle: Chunk Borders\n"); + }); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } @@ -229,6 +268,7 @@ std::unique_ptr buildDebugOverlay(vui::Renderer &r, vui::SceneManage auto &mem = scene->text("Mem: 0MB", {10, 90, 300, 20}, dbgText); mem.name = "memory"; + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } @@ -241,6 +281,7 @@ std::unique_ptr buildTimer(vui::Renderer &r, vui::SceneManager &scen auto &spinner = scene->text("Loading...", {440, 340, 400, 40}, s.titleText); spinner.name = "spinner"; + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } @@ -259,6 +300,7 @@ std::unique_ptr buildKeyboard(vui::Renderer &r, vui::SceneManager &s if (scenes.depth() > 1) scenes.pop(); }); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } @@ -269,6 +311,7 @@ std::unique_ptr buildQuadrantSignin(vui::Renderer &r, vui::SceneMana scene->text("Press Start to Join", {340, 316, 600, 88}, s.titleText); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } @@ -286,9 +329,14 @@ std::unique_ptr buildInGameSaveManagement(vui::Renderer &r, vui::Sce list.textColor = vui::White; auto &hstack = scene->hstack(340, 600, 20); - hstack.button("Copy", s.menuButtonNarrow, []() {}); - hstack.button("Delete", s.menuButtonNarrow, []() {}); + hstack.button("Copy", s.menuButtonNarrow, []() { + fprintf(stderr, "[MCE] Copy save requested\n"); + }); + hstack.button("Delete", s.menuButtonNarrow, []() { + fprintf(stderr, "[MCE] Delete save requested\n"); + }); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/HelpAndOptionsScene.cpp b/Minecraft.Client/CrossPlatform/scenes/HelpAndOptionsScene.cpp index 6b2c50d..2ea2ab6 100644 --- a/Minecraft.Client/CrossPlatform/scenes/HelpAndOptionsScene.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/HelpAndOptionsScene.cpp @@ -34,6 +34,7 @@ std::unique_ptr buildHelpAndOptions(vui::Renderer &r, vui::SceneMana scenes.push(buildCredits(r, scenes, s)); }); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/HowToPlayPageScene.cpp b/Minecraft.Client/CrossPlatform/scenes/HowToPlayPageScene.cpp index ea6d2f0..f8af777 100644 --- a/Minecraft.Client/CrossPlatform/scenes/HowToPlayPageScene.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/HowToPlayPageScene.cpp @@ -26,6 +26,7 @@ std::unique_ptr buildHowToPlayPage(vui::Renderer &r, vui::SceneManag body->name = "page_body"; contentPanel.addChild(std::move(body)); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/HowToPlayScene.cpp b/Minecraft.Client/CrossPlatform/scenes/HowToPlayScene.cpp index 4e17390..8758279 100644 --- a/Minecraft.Client/CrossPlatform/scenes/HowToPlayScene.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/HowToPlayScene.cpp @@ -25,6 +25,7 @@ std::unique_ptr buildHowToPlay(vui::Renderer &r, vui::SceneManager & auto &contentPanel = scene->panel({480, 260, 660, 400}, s.settingsPanel); contentPanel.name = "content_panel"; + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/InventoryScene.cpp b/Minecraft.Client/CrossPlatform/scenes/InventoryScene.cpp index 97db2b2..084e5e8 100644 --- a/Minecraft.Client/CrossPlatform/scenes/InventoryScene.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/InventoryScene.cpp @@ -62,6 +62,7 @@ std::unique_ptr buildInventory(vui::Renderer &r, vui::SceneManager & outputSlot->w = 48; outputSlot->h = 48; scene->root.addChild(std::move(outputSlot)); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/JoinMenuScene.cpp b/Minecraft.Client/CrossPlatform/scenes/JoinMenuScene.cpp index 980448d..42e8648 100644 --- a/Minecraft.Client/CrossPlatform/scenes/JoinMenuScene.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/JoinMenuScene.cpp @@ -1,6 +1,7 @@ #include "vui.h" #include "LceStyles.h" #include +#include namespace lce { @@ -14,6 +15,11 @@ std::unique_ptr buildJoinMenu(vui::Renderer &r, vui::SceneManager &s scene->text("Join Game", {440, 200, 400, 40}, s.titleText); + vui::TextStyle dimLabel = s.labelText; + dimLabel.color = vui::Color::rgb(160, 160, 160); + dimLabel.align = vui::Alignment::Center; + scene->text("No games found", {340, 400, 600, 40}, dimLabel); + auto &list = scene->scrollList({340, 260, 600, 320}, 8, 40); list.name = "game_list"; list.font = s.fontMenu; @@ -22,8 +28,18 @@ std::unique_ptr buildJoinMenu(vui::Renderer &r, vui::SceneManager &s list.hoverColor = vui::Color::rgb(50, 50, 90); list.bgColor = vui::Color::rgba(0, 0, 0, 120); - scene->button("Join Game", {415, 600, 450, 40}, s.menuButton, []() {}); + scene->button("Join Game", {415, 600, 450, 40}, s.menuButton, [scene_ptr = scene.get()]() { + vui::Element *listEl = scene_ptr->findElement("game_list"); + if (listEl == nullptr) return; + vui::ScrollList *gameList = static_cast(listEl); + if (gameList->selectedIndex < 0 || gameList->items.empty()) { + fprintf(stderr, "[MCE] Join: no game selected\n"); + return; + } + fprintf(stderr, "[MCE] Join: would join game index %d\n", gameList->selectedIndex); + }); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/LceStyles.h b/Minecraft.Client/CrossPlatform/scenes/LceStyles.h index 8f9da1d..36afbfd 100644 --- a/Minecraft.Client/CrossPlatform/scenes/LceStyles.h +++ b/Minecraft.Client/CrossPlatform/scenes/LceStyles.h @@ -1,25 +1,37 @@ #pragma once #include "vui.h" +#include "Options.h" #include #include namespace lce { +inline float &panoramaScrollPos() { + static float pos = 0; + return pos; +} + inline void addScrollingPanorama(vui::Scene &scene, int panoramaTex) { float panAR = 1230.0f / 216.0f; float panW = 720.0f * panAR; auto &pan1 = scene.image(panoramaTex, {0, 0, panW, 720}); pan1.name = "pan1"; + pan1.posX = panoramaScrollPos(); auto &pan2 = scene.image(panoramaTex, {panW, 0, panW, 720}); pan2.name = "pan2"; + pan2.posX = pan1.posX + panW; pan1.onUpdate = [panW, &pan2](vui::Element &el, float) { - el.posX -= 0.3f; - if (el.posX <= -panW) el.posX += panW; - pan2.posX = el.posX + panW; + float &gpos = panoramaScrollPos(); + gpos -= 0.3f; + if (gpos <= -panW) gpos += panW; + el.posX = gpos; + pan2.posX = gpos + panW; }; } struct LceStyles { + Options *opts = nullptr; + int btnNormTex = -1; int btnHoverTex = -1; int btnOutlineTex = -1; @@ -31,6 +43,8 @@ struct LceStyles { int sliderTermTex = -1; int sliderHighlightTex = -1; int checkboxBgTex = -1; + int checkboxHoverTex = -1; + int checkboxTickTex = -1; int textInputBgTex = -1; int textInputBorderTex = -1; int textInputActiveBorderTex = -1; @@ -123,7 +137,7 @@ struct LceStyles { std::string win = assetBase + "/ui/skinHDWin/"; std::string hud = assetBase + "/ui/skinHDGraphicsHud/"; std::string ig = assetBase + "/ui/skinHDGraphicsInGame/"; - std::string fontPath = assetBase + "/Mojangles.ttf"; + std::string fontPath = assetBase + "/fonts/Mojangles.ttf"; btnNormTex = r.loadTexture((hd + "191_FJ_MainMenuButton_Norm.png").c_str()); btnHoverTex = r.loadTexture((hd + "189_MainMenuButton_Over.png").c_str()); @@ -136,6 +150,8 @@ struct LceStyles { sliderTermTex = r.loadTexture((hd + "179_SliderTrack_Terminator.png").c_str()); sliderHighlightTex = r.loadTexture((hd + "180_SliderBackgroundHighlight.png").c_str()); checkboxBgTex = r.loadTexture((hd + "178_FJ_Tickbox_Background.png").c_str()); + checkboxHoverTex = r.loadTexture((hd + "176_Tickbox_Over.png").c_str()); + checkboxTickTex = r.loadTexture((hd + "177_Tick.png").c_str()); listBtnNormTex = r.loadTexture((hd + "185_ListButton_Norm.png").c_str()); listBtnHoverTex = r.loadTexture((hd + "183_ListButton_Over.png").c_str()); listBtnOutlineTex = r.loadTexture((hd + "184_ListButton_Outline.png").c_str()); @@ -286,8 +302,8 @@ struct LceStyles { wideSlider.width = 446; settingsCheckbox.bgTex = checkboxBgTex; - settingsCheckbox.hoverTex = checkboxBgTex; - settingsCheckbox.checkTex = -1; + settingsCheckbox.hoverTex = checkboxHoverTex; + settingsCheckbox.checkTex = checkboxTickTex; settingsCheckbox.font = fontMenu; settingsCheckbox.textColor = vui::White; settingsCheckbox.size = 24; diff --git a/Minecraft.Client/CrossPlatform/scenes/LeaderboardsScene.cpp b/Minecraft.Client/CrossPlatform/scenes/LeaderboardsScene.cpp index 313b9fc..70faf62 100644 --- a/Minecraft.Client/CrossPlatform/scenes/LeaderboardsScene.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/LeaderboardsScene.cpp @@ -25,6 +25,7 @@ std::unique_ptr buildLeaderboards(vui::Renderer &r, vui::SceneManage boardList.hoverColor = vui::Color::rgb(50, 50, 90); boardList.bgColor = vui::Color::rgba(0, 0, 0, 120); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/LoadMenuScene.cpp b/Minecraft.Client/CrossPlatform/scenes/LoadMenuScene.cpp index 1ba74f1..7a868fc 100644 --- a/Minecraft.Client/CrossPlatform/scenes/LoadMenuScene.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/LoadMenuScene.cpp @@ -1,6 +1,7 @@ #include "vui.h" #include "LceStyles.h" #include +#include namespace lce { @@ -41,6 +42,22 @@ std::unique_ptr buildLoadMenu(vui::Renderer &r, vui::SceneManager &s btn1->posX = 25; btn1->posY = 129; btn1->w = 440; btn1->h = 40; btn1->name = "game_mode_toggle"; + btn1->onClick = [scene_ptr = scene.get()]() { + vui::Element *el = scene_ptr->findElement("game_mode_toggle"); + if (el == nullptr) return; + vui::Button *btn = static_cast(el); + + vui::Element *labelEl = scene_ptr->findElement("game_mode"); + vui::Text *labelText = labelEl ? static_cast(labelEl) : nullptr; + + if (btn->label == "Game Mode: Survival") { + btn->label = "Game Mode: Creative"; + if (labelText) labelText->text = "Creative"; + } else { + btn->label = "Game Mode: Survival"; + if (labelText) labelText->text = "Survival"; + } + }; mainPanel.addChild(std::move(btn1)); auto sl1 = std::make_unique(); @@ -49,6 +66,22 @@ std::unique_ptr buildLoadMenu(vui::Renderer &r, vui::SceneManager &s sl1->posX = 22; sl1->posY = 177; sl1->w = 446; sl1->h = 38; sl1->name = "difficulty"; + sl1->label = "Difficulty: Normal"; + sl1->onChange = [scene_ptr = scene.get()](float v) { + int diff = (int)(v + 0.5f); + if (diff < 0) diff = 0; + if (diff > 3) diff = 3; + + const char *names[4] = {"Peaceful", "Easy", "Normal", "Hard"}; + + vui::Element *el = scene_ptr->findElement("difficulty"); + if (el != nullptr) { + vui::Slider *sl = static_cast(el); + char buf[64]; + snprintf(buf, sizeof(buf), "Difficulty: %s", names[diff]); + sl->label = buf; + } + }; mainPanel.addChild(std::move(sl1)); auto btn2 = std::make_unique(); @@ -56,6 +89,10 @@ std::unique_ptr buildLoadMenu(vui::Renderer &r, vui::SceneManager &s btn2->style = s.settingsPanelButton; btn2->posX = 25; btn2->posY = 334; btn2->w = 440; btn2->h = 40; + btn2->onClick = [&scenes, &r, &s]() { + extern std::unique_ptr buildLaunchMoreOptions(vui::Renderer &, vui::SceneManager &, LceStyles &); + scenes.push(buildLaunchMoreOptions(r, scenes, s)); + }; mainPanel.addChild(std::move(btn2)); auto btn3 = std::make_unique(); @@ -64,8 +101,13 @@ std::unique_ptr buildLoadMenu(vui::Renderer &r, vui::SceneManager &s btn3->posX = 25; btn3->posY = 384; btn3->w = 440; btn3->h = 40; btn3->name = "play"; + btn3->onClick = [&scenes]() { + fprintf(stderr, "[MCE] Load: Play pressed (save loading not yet implemented)\n"); + scenes.pop(); + }; mainPanel.addChild(std::move(btn3)); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/LoadOrJoinScene.cpp b/Minecraft.Client/CrossPlatform/scenes/LoadOrJoinScene.cpp index 4bab24e..707712e 100644 --- a/Minecraft.Client/CrossPlatform/scenes/LoadOrJoinScene.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/LoadOrJoinScene.cpp @@ -26,6 +26,7 @@ std::unique_ptr buildLoadOrJoin(vui::Renderer &r, vui::SceneManager scenes.push(buildJoinMenu(r, scenes, s)); }); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/MiscScenes.cpp b/Minecraft.Client/CrossPlatform/scenes/MiscScenes.cpp index f5bcf17..5a87ba5 100644 --- a/Minecraft.Client/CrossPlatform/scenes/MiscScenes.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/MiscScenes.cpp @@ -2,6 +2,7 @@ #include "LceStyles.h" #include #include +#include namespace lce { @@ -28,6 +29,7 @@ std::unique_ptr buildIntro(vui::Renderer &r, vui::SceneManager &scen } }; + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } @@ -80,6 +82,7 @@ std::unique_ptr buildMessageBox(vui::Renderer &r, vui::SceneManager }; panel.addChild(std::move(btn2)); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } @@ -102,6 +105,7 @@ std::unique_ptr buildEULA(vui::Renderer &r, vui::SceneManager &scene if (scenes.depth() > 1) scenes.pop(); }); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } @@ -130,6 +134,7 @@ std::unique_ptr buildSaveMessage(vui::Renderer &r, vui::SceneManager body->name = "body"; panel.addChild(std::move(body)); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } @@ -145,6 +150,7 @@ std::unique_ptr buildConnectingProgress(vui::Renderer &r, vui::Scene }); cancel.name = "cancel"; + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } @@ -161,6 +167,7 @@ std::unique_ptr buildFullscreenProgress(vui::Renderer &r, vui::Scene auto &dirtBg = scene->image(s.dirtTex, {0, 600, 1280, 120}); dirtBg.name = "dirt_bg"; + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } @@ -202,6 +209,7 @@ std::unique_ptr buildCredits(vui::Renderer &r, vui::SceneManager &sc holder.text("Music", creditTitle); holder.text("C418 - Daniel Rosenfeld", creditBody); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } @@ -236,6 +244,7 @@ std::unique_ptr buildEndPoem(vui::Renderer &r, vui::SceneManager &sc holder.text("I like this player. It played well.", poemGreen); holder.text("It did not give up.", poemBlue); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } @@ -252,11 +261,14 @@ std::unique_ptr buildTrialExitUpsell(vui::Renderer &r, vui::SceneMan ); body.wrap = true; - scene->button("Unlock Full Game", {415, 440, 450, 40}, s.menuButton, []() {}); + scene->button("Unlock Full Game", {415, 440, 450, 40}, s.menuButton, []() { + fprintf(stderr, "[MCE] Unlock Full Game requested (store not available)\n"); + }); scene->button("Exit", {415, 500, 450, 40}, s.menuButton, []() { exit(0); }); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/PauseMenuScene.cpp b/Minecraft.Client/CrossPlatform/scenes/PauseMenuScene.cpp index ccd612c..f33e94a 100644 --- a/Minecraft.Client/CrossPlatform/scenes/PauseMenuScene.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/PauseMenuScene.cpp @@ -1,6 +1,7 @@ #include "vui.h" #include "LceStyles.h" #include +#include namespace lce { @@ -25,12 +26,19 @@ std::unique_ptr buildPauseMenu(vui::Renderer &r, vui::SceneManager & extern std::unique_ptr buildLeaderboards(vui::Renderer &, vui::SceneManager &, LceStyles &); scenes.push(buildLeaderboards(r, scenes, s)); }); - stack.button("Achievements", s.menuButtonNarrow, []() {}); - stack.button("Save Game", s.menuButtonNarrow, []() {}); + stack.button("Achievements", s.menuButtonNarrow, []() { + fprintf(stderr, "[MCE] Achievements not yet implemented\n"); + }); + stack.button("Save Game", s.menuButtonNarrow, [&scenes, &r, &s]() { + fprintf(stderr, "[MCE] Save Game requested\n"); + extern std::unique_ptr buildSaveMessage(vui::Renderer &, vui::SceneManager &, LceStyles &); + scenes.push(buildSaveMessage(r, scenes, s)); + }); stack.button("Exit Game", s.menuButtonNarrow, [&scenes]() { while (scenes.depth() > 1) scenes.pop(); }); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/SettingsAudioScene.cpp b/Minecraft.Client/CrossPlatform/scenes/SettingsAudioScene.cpp index b8e02fc..13acce0 100644 --- a/Minecraft.Client/CrossPlatform/scenes/SettingsAudioScene.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/SettingsAudioScene.cpp @@ -6,6 +6,7 @@ namespace lce { std::unique_ptr buildSettingsAudio(vui::Renderer &r, vui::SceneManager &scenes, LceStyles &s) { auto scene = std::make_unique("settings_audio"); + Options *opts = s.opts; addScrollingPanorama(*scene, s.panoramaTex); @@ -16,20 +17,25 @@ std::unique_ptr buildSettingsAudio(vui::Renderer &r, vui::SceneManag auto sl1 = std::make_unique(); sl1->style = s.settingsSlider; - sl1->minVal = 0; sl1->maxVal = 100; sl1->value = 50; + sl1->minVal = 0; sl1->maxVal = 1; sl1->value = opts ? opts->music : 1.0f; sl1->posX = 12; sl1->posY = 12; sl1->w = 306; sl1->h = 38; sl1->name = "music_volume"; + sl1->label = "Music"; + sl1->onChange = [opts](float v) { if (opts) opts->set(Options::Option::MUSIC, v); }; panel.addChild(std::move(sl1)); auto sl2 = std::make_unique(); sl2->style = s.settingsSlider; - sl2->minVal = 0; sl2->maxVal = 100; sl2->value = 50; + sl2->minVal = 0; sl2->maxVal = 1; sl2->value = opts ? opts->sound : 1.0f; sl2->posX = 12; sl2->posY = 56; sl2->w = 306; sl2->h = 38; sl2->name = "sound_volume"; + sl2->label = "Sound"; + sl2->onChange = [opts](float v) { if (opts) opts->set(Options::Option::SOUND, v); }; panel.addChild(std::move(sl2)); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/SettingsControlScene.cpp b/Minecraft.Client/CrossPlatform/scenes/SettingsControlScene.cpp index 20e0fa6..f42339f 100644 --- a/Minecraft.Client/CrossPlatform/scenes/SettingsControlScene.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/SettingsControlScene.cpp @@ -6,6 +6,7 @@ namespace lce { std::unique_ptr buildSettingsControl(vui::Renderer &r, vui::SceneManager &scenes, LceStyles &s) { auto scene = std::make_unique("settings_control"); + Options *opts = s.opts; addScrollingPanorama(*scene, s.panoramaTex); @@ -16,20 +17,25 @@ std::unique_ptr buildSettingsControl(vui::Renderer &r, vui::SceneMan auto sl1 = std::make_unique(); sl1->style = s.settingsSlider; - sl1->minVal = 0; sl1->maxVal = 200; sl1->value = 100; + sl1->minVal = 0; sl1->maxVal = 1; sl1->value = opts ? opts->sensitivity : 0.5f; sl1->posX = 12; sl1->posY = 12; sl1->w = 306; sl1->h = 38; sl1->name = "sensitivity_game"; + sl1->label = "Look Sensitivity"; + sl1->onChange = [opts](float v) { if (opts) opts->set(Options::Option::SENSITIVITY, v); }; panel.addChild(std::move(sl1)); auto sl2 = std::make_unique(); sl2->style = s.settingsSlider; - sl2->minVal = 0; sl2->maxVal = 200; sl2->value = 100; + sl2->minVal = 0; sl2->maxVal = 1; sl2->value = opts ? opts->sensitivity : 0.5f; sl2->posX = 12; sl2->posY = 56; sl2->w = 306; sl2->h = 38; sl2->name = "sensitivity_menu"; + sl2->label = "Menu Sensitivity"; + sl2->onChange = [](float) {}; panel.addChild(std::move(sl2)); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/SettingsGraphicsScene.cpp b/Minecraft.Client/CrossPlatform/scenes/SettingsGraphicsScene.cpp index a549dfb..141cbdd 100644 --- a/Minecraft.Client/CrossPlatform/scenes/SettingsGraphicsScene.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/SettingsGraphicsScene.cpp @@ -1,11 +1,15 @@ #include "vui.h" #include "LceStyles.h" +#include "../../Windows64/4JLibs/inc/4J_Render.h" #include +extern C4JRender RenderManager; + namespace lce { std::unique_ptr buildSettingsGraphics(vui::Renderer &r, vui::SceneManager &scenes, LceStyles &s) { auto scene = std::make_unique("settings_graphics"); + Options *opts = s.opts; addScrollingPanorama(*scene, s.panoramaTex); @@ -17,43 +21,55 @@ std::unique_ptr buildSettingsGraphics(vui::Renderer &r, vui::SceneMa auto cb1 = std::make_unique(); cb1->label = "Render Clouds"; cb1->style = s.settingsCheckbox; - cb1->checked = true; + cb1->checked = opts ? opts->renderClouds : true; cb1->posX = 14; cb1->posY = 12; cb1->w = 300; cb1->h = 32; + cb1->onChange = [opts](bool v) { if (opts) opts->renderClouds = v; }; panel.addChild(std::move(cb1)); auto cb2 = std::make_unique(); - cb2->label = "Render Bedrock Fog"; + cb2->label = "Fancy Graphics"; cb2->style = s.settingsCheckbox; - cb2->checked = true; + cb2->checked = opts ? opts->fancyGraphics : true; cb2->posX = 14; cb2->posY = 48; cb2->w = 300; cb2->h = 32; + cb2->onChange = [opts](bool v) { if (opts) opts->fancyGraphics = v; }; panel.addChild(std::move(cb2)); auto cb3 = std::make_unique(); - cb3->label = "Custom Skin Animation"; + cb3->label = "Ambient Occlusion"; cb3->style = s.settingsCheckbox; - cb3->checked = true; + cb3->checked = opts ? opts->ambientOcclusion : true; cb3->posX = 14; cb3->posY = 84; cb3->w = 300; cb3->h = 32; + cb3->onChange = [opts](bool v) { if (opts) opts->ambientOcclusion = v; }; panel.addChild(std::move(cb3)); auto sl1 = std::make_unique(); sl1->style = s.settingsSlider; - sl1->minVal = 0; sl1->maxVal = 100; sl1->value = 50; + sl1->minVal = 0; sl1->maxVal = 1; sl1->value = opts ? opts->gamma : 0.5f; sl1->posX = 12; sl1->posY = 124; sl1->w = 306; sl1->h = 38; sl1->name = "gamma"; + sl1->label = "Brightness"; + sl1->onChange = [opts](float v) { + if (opts) opts->set(Options::Option::GAMMA, v); + unsigned short usGamma = static_cast(v * 32768.0f); + RenderManager.UpdateGamma(usGamma); + }; panel.addChild(std::move(sl1)); auto sl2 = std::make_unique(); sl2->style = s.settingsSlider; - sl2->minVal = 0; sl2->maxVal = 100; sl2->value = 100; + sl2->minVal = 30; sl2->maxVal = 110; sl2->value = opts ? opts->fov : 70; sl2->posX = 12; sl2->posY = 168; sl2->w = 306; sl2->h = 38; - sl2->name = "interface_opacity"; + sl2->name = "fov"; + sl2->label = "FOV"; + sl2->onChange = [opts](float v) { if (opts) opts->fov = v; }; panel.addChild(std::move(sl2)); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/SettingsHubScene.cpp b/Minecraft.Client/CrossPlatform/scenes/SettingsHubScene.cpp index 64d7fd4..6200049 100644 --- a/Minecraft.Client/CrossPlatform/scenes/SettingsHubScene.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/SettingsHubScene.cpp @@ -1,6 +1,7 @@ #include "vui.h" #include "LceStyles.h" #include +#include namespace lce { @@ -33,8 +34,11 @@ std::unique_ptr buildSettingsHub(vui::Renderer &r, vui::SceneManager extern std::unique_ptr buildSettingsUI(vui::Renderer &, vui::SceneManager &, LceStyles &); scenes.push(buildSettingsUI(r, scenes, s)); }); - stack.button("Reset to Defaults", s.menuButton, []() {}); + stack.button("Reset to Defaults", s.menuButton, []() { + fprintf(stderr, "[MCE] Reset to Defaults requested\n"); + }); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/SettingsOptionsScene.cpp b/Minecraft.Client/CrossPlatform/scenes/SettingsOptionsScene.cpp index 2e18588..5386138 100644 --- a/Minecraft.Client/CrossPlatform/scenes/SettingsOptionsScene.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/SettingsOptionsScene.cpp @@ -6,6 +6,7 @@ namespace lce { std::unique_ptr buildSettingsOptions(vui::Renderer &r, vui::SceneManager &scenes, LceStyles &s) { auto scene = std::make_unique("settings_options"); + Options *opts = s.opts; addScrollingPanorama(*scene, s.panoramaTex); @@ -17,17 +18,19 @@ std::unique_ptr buildSettingsOptions(vui::Renderer &r, vui::SceneMan auto cb1 = std::make_unique(); cb1->label = "View Bobbing"; cb1->style = s.settingsCheckbox; - cb1->checked = true; + cb1->checked = opts ? opts->bobView : true; cb1->posX = 14; cb1->posY = 12; cb1->w = 300; cb1->h = 32; + cb1->onChange = [opts](bool v) { if (opts) opts->bobView = v; }; panel.addChild(std::move(cb1)); auto cb2 = std::make_unique(); cb2->label = "Hints"; cb2->style = s.settingsCheckbox; - cb2->checked = true; + cb2->checked = opts ? !opts->hideGui : true; cb2->posX = 14; cb2->posY = 42; cb2->w = 300; cb2->h = 32; + cb2->onChange = [opts](bool v) { if (opts) opts->hideGui = !v; }; panel.addChild(std::move(cb2)); auto cb3 = std::make_unique(); @@ -36,6 +39,7 @@ std::unique_ptr buildSettingsOptions(vui::Renderer &r, vui::SceneMan cb3->checked = true; cb3->posX = 14; cb3->posY = 72; cb3->w = 300; cb3->h = 32; + cb3->onChange = [](bool) {}; panel.addChild(std::move(cb3)); auto cb4 = std::make_unique(); @@ -44,6 +48,7 @@ std::unique_ptr buildSettingsOptions(vui::Renderer &r, vui::SceneMan cb4->checked = true; cb4->posX = 14; cb4->posY = 102; cb4->w = 300; cb4->h = 32; + cb4->onChange = [](bool) {}; panel.addChild(std::move(cb4)); auto cb5 = std::make_unique(); @@ -52,22 +57,30 @@ std::unique_ptr buildSettingsOptions(vui::Renderer &r, vui::SceneMan cb5->checked = false; cb5->posX = 14; cb5->posY = 132; cb5->w = 300; cb5->h = 32; + cb5->onChange = [](bool) {}; panel.addChild(std::move(cb5)); auto sl1 = std::make_unique(); sl1->style = s.settingsSlider; - sl1->minVal = 0; sl1->maxVal = 8; sl1->value = 4; + sl1->minVal = 0; sl1->maxVal = 3; sl1->value = opts ? (float)opts->viewDistance : 0; sl1->posX = 12; sl1->posY = 170; sl1->w = 306; sl1->h = 38; + sl1->name = "render_distance"; + sl1->label = "Render Distance"; + sl1->onChange = [opts](float v) { if (opts) opts->viewDistance = (int)(v + 0.5f); }; panel.addChild(std::move(sl1)); auto sl2 = std::make_unique(); sl2->style = s.settingsSlider; - sl2->minVal = 0; sl2->maxVal = 3; sl2->value = 2; + sl2->minVal = 0; sl2->maxVal = 3; sl2->value = opts ? (float)opts->difficulty : 2; sl2->posX = 12; sl2->posY = 214; sl2->w = 306; sl2->h = 38; + sl2->name = "difficulty"; + sl2->label = "Difficulty"; + sl2->onChange = [opts](float v) { if (opts) opts->difficulty = (int)(v + 0.5f); }; panel.addChild(std::move(sl2)); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/SettingsUIScene.cpp b/Minecraft.Client/CrossPlatform/scenes/SettingsUIScene.cpp index 0712405..4841ed8 100644 --- a/Minecraft.Client/CrossPlatform/scenes/SettingsUIScene.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/SettingsUIScene.cpp @@ -6,6 +6,7 @@ namespace lce { std::unique_ptr buildSettingsUI(vui::Renderer &r, vui::SceneManager &scenes, LceStyles &s) { auto scene = std::make_unique("settings_ui"); + Options *opts = s.opts; addScrollingPanorama(*scene, s.panoramaTex); @@ -17,9 +18,10 @@ std::unique_ptr buildSettingsUI(vui::Renderer &r, vui::SceneManager auto cb1 = std::make_unique(); cb1->label = "Display HUD"; cb1->style = s.settingsCheckbox; - cb1->checked = true; + cb1->checked = opts ? !opts->hideGui : true; cb1->posX = 14; cb1->posY = 12; cb1->w = 300; cb1->h = 32; + cb1->onChange = [opts](bool v) { if (opts) opts->hideGui = !v; }; panel.addChild(std::move(cb1)); auto cb2 = std::make_unique(); @@ -28,6 +30,7 @@ std::unique_ptr buildSettingsUI(vui::Renderer &r, vui::SceneManager cb2->checked = true; cb2->posX = 14; cb2->posY = 48; cb2->w = 300; cb2->h = 32; + cb2->onChange = [](bool) {}; panel.addChild(std::move(cb2)); auto cb3 = std::make_unique(); @@ -36,6 +39,7 @@ std::unique_ptr buildSettingsUI(vui::Renderer &r, vui::SceneManager cb3->checked = true; cb3->posX = 14; cb3->posY = 84; cb3->w = 300; cb3->h = 32; + cb3->onChange = [](bool) {}; panel.addChild(std::move(cb3)); auto cb4 = std::make_unique(); @@ -44,6 +48,7 @@ std::unique_ptr buildSettingsUI(vui::Renderer &r, vui::SceneManager cb4->checked = true; cb4->posX = 14; cb4->posY = 120; cb4->w = 300; cb4->h = 32; + cb4->onChange = [](bool) {}; panel.addChild(std::move(cb4)); auto cb5 = std::make_unique(); @@ -52,6 +57,7 @@ std::unique_ptr buildSettingsUI(vui::Renderer &r, vui::SceneManager cb5->checked = false; cb5->posX = 14; cb5->posY = 156; cb5->w = 300; cb5->h = 32; + cb5->onChange = [](bool) {}; panel.addChild(std::move(cb5)); auto cb6 = std::make_unique(); @@ -60,24 +66,30 @@ std::unique_ptr buildSettingsUI(vui::Renderer &r, vui::SceneManager cb6->checked = false; cb6->posX = 14; cb6->posY = 188; cb6->w = 300; cb6->h = 32; + cb6->onChange = [](bool) {}; panel.addChild(std::move(cb6)); auto sl1 = std::make_unique(); sl1->style = s.settingsSlider; - sl1->minVal = 1; sl1->maxVal = 3; sl1->value = 2; + sl1->minVal = 0; sl1->maxVal = 3; sl1->value = opts ? (float)opts->guiScale : 2; sl1->posX = 12; sl1->posY = 230; sl1->w = 306; sl1->h = 38; sl1->name = "ui_size"; + sl1->label = "UI Size"; + sl1->onChange = [opts](float v) { if (opts) opts->guiScale = (int)(v + 0.5f); }; panel.addChild(std::move(sl1)); auto sl2 = std::make_unique(); sl2->style = s.settingsSlider; - sl2->minVal = 1; sl2->maxVal = 3; sl2->value = 2; + sl2->minVal = 0; sl2->maxVal = 3; sl2->value = opts ? (float)opts->guiScale : 2; sl2->posX = 12; sl2->posY = 274; sl2->w = 306; sl2->h = 38; sl2->name = "ui_size_split"; + sl2->label = "Splitscreen UI Size"; + sl2->onChange = [](float) {}; panel.addChild(std::move(sl2)); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/SkinSelectScene.cpp b/Minecraft.Client/CrossPlatform/scenes/SkinSelectScene.cpp index 3da0803..ab730a6 100644 --- a/Minecraft.Client/CrossPlatform/scenes/SkinSelectScene.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/SkinSelectScene.cpp @@ -25,6 +25,7 @@ std::unique_ptr buildSkinSelect(vui::Renderer &r, vui::SceneManager scene->text("", {540, 550, 200, 26}, s.labelTextCenter); + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/CrossPlatform/scenes/TradingScene.cpp b/Minecraft.Client/CrossPlatform/scenes/TradingScene.cpp index e8d88d2..fe148dd 100644 --- a/Minecraft.Client/CrossPlatform/scenes/TradingScene.cpp +++ b/Minecraft.Client/CrossPlatform/scenes/TradingScene.cpp @@ -63,6 +63,7 @@ std::unique_ptr buildTrading(vui::Renderer &r, vui::SceneManager &sc scene->root.addChild(std::move(slot)); } + scene->onBack = [&scenes]() { scenes.pop(); }; return scene; } diff --git a/Minecraft.Client/Platform_Libs/Dev/RenderVk/VulkanBootstrapApp.h b/Minecraft.Client/Platform_Libs/Dev/RenderVk/VulkanBootstrapApp.h index 24e7a86..49ed997 100644 --- a/Minecraft.Client/Platform_Libs/Dev/RenderVk/VulkanBootstrapApp.h +++ b/Minecraft.Client/Platform_Libs/Dev/RenderVk/VulkanBootstrapApp.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -122,6 +123,9 @@ public: uint32_t height, const void *pixelData); + std::function overlayCallback; + std::function swapchainRecreatedCallback; + VkDevice getDevice() const { return device_; } VkPhysicalDevice getPhysicalDevice() const { return physicalDevice_; } VkQueue getGraphicsQueue() const { return graphicsQueue_; } diff --git a/Minecraft.Client/Platform_Libs/Dev/RenderVk/VulkanBootstrapAppRender.cpp b/Minecraft.Client/Platform_Libs/Dev/RenderVk/VulkanBootstrapAppRender.cpp index cccd777..d359ba6 100644 --- a/Minecraft.Client/Platform_Libs/Dev/RenderVk/VulkanBootstrapAppRender.cpp +++ b/Minecraft.Client/Platform_Libs/Dev/RenderVk/VulkanBootstrapAppRender.cpp @@ -109,6 +109,10 @@ void VulkanBootstrapApp::recreateSwapchain() createFramebuffers(); createCommandBuffers(); setViewportRect(0, 0, swapchainExtent_.width, swapchainExtent_.height); + + if (swapchainRecreatedCallback) { + swapchainRecreatedCallback(); + } } void VulkanBootstrapApp::cleanupSwapchain() @@ -1082,6 +1086,10 @@ void VulkanBootstrapApp::recordCommandBuffer( } vkCmdEndRenderPass(commandBuffer); + if (overlayCallback) { + overlayCallback(commandBuffer, imageIndex); + } + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { throw std::runtime_error("vkEndCommandBuffer failed"); diff --git a/Minecraft.Client/Platform_Libs/Dev/RenderVk/VulkanRenderManager.cpp b/Minecraft.Client/Platform_Libs/Dev/RenderVk/VulkanRenderManager.cpp index 2c0e646..02fd4bd 100644 --- a/Minecraft.Client/Platform_Libs/Dev/RenderVk/VulkanRenderManager.cpp +++ b/Minecraft.Client/Platform_Libs/Dev/RenderVk/VulkanRenderManager.cpp @@ -13,9 +13,11 @@ #define STB_IMAGE_IMPLEMENTATION #define STBI_ONLY_PNG #define STBI_ONLY_JPEG -#define STBI_NO_STDIO #include "stb_image.h" +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" + #include "VulkanBootstrapApp.h" #include @@ -31,6 +33,12 @@ VulkanBootstrapApp g_vulkanBackend; +// Brightness multiplier derived from the gamma setting. Updated by +// C4JRender::UpdateGamma each time the user moves the brightness slider. +// Range maps from ucGamma 0-32768 -> multiplier ~0.5 (dark) to ~2.0 (bright), +// with the midpoint (gamma 0.5) producing a multiplier of 1.0 (no change). +static float g_brightnessMul = 1.0f; + namespace { @@ -412,9 +420,9 @@ ConvertParams buildConvertParams() } else { - params.colourScale[0] = context.renderState.colour[0]; - params.colourScale[1] = context.renderState.colour[1]; - params.colourScale[2] = context.renderState.colour[2]; + params.colourScale[0] = context.renderState.colour[0] * g_brightnessMul; + params.colourScale[1] = context.renderState.colour[1] * g_brightnessMul; + params.colourScale[2] = context.renderState.colour[2] * g_brightnessMul; params.colourScale[3] = context.renderState.colour[3]; } // Blend factor alpha applies to per-vertex alpha in both recording and @@ -909,8 +917,15 @@ void C4JRender::Shutdown() void C4JRender::Tick() {} void C4JRender::UpdateGamma(unsigned short usGamma) { - extern void Windows64_UpdateGamma(unsigned short); - Windows64_UpdateGamma(usGamma); + // usGamma range is 0-32768 (ucGamma 0-100 scaled by 327.68). + // Map to a 0.0-1.0 normalised value, then derive a brightness multiplier + // using the same power curve the old glfwSetGamma path used: + // multiplier = 0.5 * 4^gamma + // gamma 0.0 -> 0.5 (darker), 0.5 -> 1.0 (normal), 1.0 -> 2.0 (bright). + float gamma = static_cast(usGamma) / 32768.0f; + if (gamma < 0.0f) gamma = 0.0f; + if (gamma > 1.0f) gamma = 1.0f; + g_brightnessMul = 0.5f * std::pow(4.0f, gamma); } void C4JRender::MatrixMode(int type) { getThreadContext().currentMatrixMode = type; } @@ -1273,8 +1288,10 @@ bool C4JRender::CBuffCall(int index, bool) currentState.textureIndex = context.currentTextureIndex; const auto &colour = context.renderState.colour; + const float bMul = g_brightnessMul; const bool needsColourModulation = - colour[0] != 1.0f || colour[1] != 1.0f || colour[2] != 1.0f || colour[3] != 1.0f; + colour[0] != 1.0f || colour[1] != 1.0f || colour[2] != 1.0f || colour[3] != 1.0f || + bMul != 1.0f; // Hoist shader variant outside the loop — the texture index and render state // don't change during display list replay, so this is invariant. @@ -1284,7 +1301,8 @@ bool C4JRender::CBuffCall(int index, bool) : determineShaderVariant(currentState.textureIndex); // Pre-build the colour modulation array once instead of per-draw. - const float colorMod[4] = {colour[0], colour[1], colour[2], colour[3]}; + // Fold brightness into the RGB channels so display list replay picks it up. + const float colorMod[4] = {colour[0] * bMul, colour[1] * bMul, colour[2] * bMul, colour[3]}; // Apply the GL_TEXTURE matrix to UV coordinates when it is non-identity. // The precompiled item mesh (ItemInHandRenderer::list) stores UVs in the diff --git a/Minecraft.Client/ThirdParty/stb_truetype.h b/Minecraft.Client/ThirdParty/stb_truetype.h new file mode 100644 index 0000000..9801164 --- /dev/null +++ b/Minecraft.Client/ThirdParty/stb_truetype.h @@ -0,0 +1,5077 @@ +// stb_truetype.h - v1.26 - public domain +// authored from 2009-2021 by Sean Barrett / RAD Game Tools +// +// ======================================================================= +// +// NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES +// +// This library does no range checking of the offsets found in the file, +// meaning an attacker can use it to read arbitrary memory. +// +// ======================================================================= +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// render glyphs to one-channel SDF bitmaps (signed-distance field/function) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// Daniel Ribeiro Maciel: basic GPOS-based kerning +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// Imanol Celaya +// Daniel Ribeiro Maciel +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe +// Cass Everitt Martins Mozeiko github:aloucks +// stoiko (Haemimont Games) Cap Petschulat github:oyvindjam +// Brian Hook Omar Cornut github:vassvik +// Walter van Niftrik Ryan Griege +// David Gow Peter LaValle +// David Given Sergey Popov +// Ivan-Assen Ivanov Giumo X. Clanjor +// Anthony Pesch Higor Euripedes +// Johan Duparc Thomas Fields +// Hou Qiming Derek Vinyard +// Rob Loach Cort Stratton +// Kenney Phillis Jr. Brian Costabile +// Ken Voskuil (kaesve) +// +// VERSION HISTORY +// +// 1.26 (2021-08-28) fix broken rasterizer +// 1.25 (2021-07-11) many fixes +// 1.24 (2020-02-05) fix warning +// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) +// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined +// 1.21 (2019-02-25) fix warning +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// Include this file in whatever places need to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversampling() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetFontVMetricsOS2() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// DETAILED USAGE: +// +// Scale: +// Select how high you want the font to be, in points or pixels. +// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute +// a scale factor SF that will be used by all other functions. +// +// Baseline: +// You need to select a y-coordinate that is the baseline of where +// your text will appear. Call GetFontBoundingBox to get the baseline-relative +// bounding box for all characters. SF*-y0 will be the distance in pixels +// that the worst-case character could extend above the baseline, so if +// you want the top edge of characters to appear at the top of the +// screen where y=0, then you would set the baseline to SF*-y0. +// +// Current point: +// Set the current point where the first character will appear. The +// first character could extend left of the current point; this is font +// dependent. You can either choose a current point that is the leftmost +// point and hope, or add some padding, or check the bounding box or +// left-side-bearing of the first character to be displayed and set +// the current point based on that. +// +// Displaying a character: +// Compute the bounding box of the character. It will contain signed values +// relative to . I.e. if it returns x0,y0,x1,y1, +// then the character should be displayed in the rectangle from +// to = 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype, e.g. if you don't +//// link with the C runtime library. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include + #define STBTT_sqrt(x) sqrt(x) + #define STBTT_pow(x,y) pow(x,y) + #endif + + #ifndef STBTT_fmod + #include + #define STBTT_fmod(x,y) fmod(x,y) + #endif + + #ifndef STBTT_cos + #include + #define STBTT_cos(x) cos(x) + #define STBTT_acos(x) acos(x) + #endif + + #ifndef STBTT_fabs + #include + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); +// Query the font vertical metrics without having to create a font first. + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is width * height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); +// If skip != 0, this tells stb_truetype to skip any codepoints for which +// there is no corresponding glyph. If skip=0, which is the default, then +// codepoints without a glyph recived the font's "missing character" glyph, +// typically an empty box by convention. + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + int skip_missing; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +// This function will determine the number of fonts in a font file. TrueType +// collection (.ttc) files may contain multiple fonts, while TrueType font +// (.ttf) files only contain one font. The number of fonts can be used for +// indexing with the previous function where the index is between zero and one +// less than the total fonts. If an error occurs, -1 is returned. + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. + +// The following structure is defined publicly so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern,gpos,svg; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. +// Returns 0 if the character codepoint is not defined in the font. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); +// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 +// table (specific to MS/Windows TTF files). +// +// Returns 1 on success (table present), 0 on failure. + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + +typedef struct stbtt_kerningentry +{ + int glyph1; // use stbtt_FindGlyphIndex + int glyph2; + int advance; +} stbtt_kerningentry; + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info); +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length); +// Retrieves a complete list of all of the kerning pairs provided by the font +// stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write. +// The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1) + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of contours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +STBTT_DEF unsigned char *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl); +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg); +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg); +// fills svg with the character's SVG data. +// returns data size or 0 if SVG not found. + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); +// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering +// is performed (see stbtt_PackSetOversampling) + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Signed Distance Function (or Field) rendering + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); +// frees the SDF bitmap allocated below + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +// These functions compute a discretized SDF field for a single character, suitable for storing +// in a single-channel texture, sampling with bilinear filtering, and testing against +// larger than some threshold to produce scalable fonts. +// info -- the font +// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap +// glyph/codepoint -- the character to generate the SDF for +// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), +// which allows effects like bit outlines +// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) +// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) +// if positive, > onedge_value is inside; if negative, < onedge_value is inside +// width,height -- output height & width of the SDF bitmap (including padding) +// xoff,yoff -- output origin of the character +// return value -- a 2D array of bytes 0..255, width*height in size +// +// pixel_dist_scale & onedge_value are a scale & bias that allows you to make +// optimal use of the limited 0..255 for your application, trading off precision +// and special effects. SDF values outside the range 0..255 are clamped to 0..255. +// +// Example: +// scale = stbtt_ScaleForPixelHeight(22) +// padding = 5 +// onedge_value = 180 +// pixel_dist_scale = 180/5.0 = 36.0 +// +// This will create an SDF bitmap in which the character is about 22 pixels +// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled +// shape, sample the SDF at each pixel and fill the pixel if the SDF value +// is greater than or equal to 180/255. (You'll actually want to antialias, +// which is beyond the scope of this example.) Additionally, you can compute +// offset outlines (e.g. to stroke the character border inside & outside, +// or only outside). For example, to fill outside the character up to 3 SDF +// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above +// choice of variables maps a range from 5 pixels outside the shape to +// 2 pixels inside the shape to 0..255; this is intended primarily for apply +// outside effects only (the interior range is needed to allow proper +// antialiasing of the font at *smaller* sizes) +// +// The function computes the SDF analytically at each SDF pixel, not by e.g. +// building a higher-res bitmap and approximating it. In theory the quality +// should be as high as possible for an SDF of this size & representation, but +// unclear if this is true in practice (perhaps building a higher-res bitmap +// and computing from that can allow drop-out prevention). +// +// The algorithm has not been optimized at all, so expect it to be slow +// if computing lots of characters or very large sizes. + + + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +// since most people won't use this, find this table the first time it's needed +static int stbtt__get_svg(stbtt_fontinfo *info) +{ + stbtt_uint32 t; + if (info->svg < 0) { + t = stbtt__find_table(info->data, info->fontstart, "SVG "); + if (t) { + stbtt_uint32 offset = ttULONG(info->data + t + 2); + info->svg = t + offset; + } else { + info->svg = 0; + } + } + return info->svg; +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + info->svg = -1; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start, last; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + last = ttUSHORT(data + endCount + 2*item); + if (unicode_codepoint < start || unicode_codepoint > last) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours < 0) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0 && vertices) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // FALLTHROUGH + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && b0 < 32) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) *x0 = r ? c.min_x : 0; + if (y0) *y0 = r ? c.min_y : 0; + if (x1) *x1 = r ? c.max_x : 0; + if (y1) *y1 = r ? c.max_y : 0; + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info) +{ + stbtt_uint8 *data = info->data + info->kern; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + return ttUSHORT(data+10); +} + +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length) +{ + stbtt_uint8 *data = info->data + info->kern; + int k, length; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + length = ttUSHORT(data+10); + if (table_length < length) + length = table_length; + + for (k = 0; k < length; k++) + { + table[k].glyph1 = ttUSHORT(data+18+(k*6)); + table[k].glyph2 = ttUSHORT(data+20+(k*6)); + table[k].advance = ttSHORT(data+22+(k*6)); + } + + return length; +} + +static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) +{ + stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); + switch (coverageFormat) { + case 1: { + stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); + + // Binary search. + stbtt_int32 l=0, r=glyphCount-1, m; + int straw, needle=glyph; + while (l <= r) { + stbtt_uint8 *glyphArray = coverageTable + 4; + stbtt_uint16 glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(glyphArray + 2 * m); + straw = glyphID; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + return m; + } + } + break; + } + + case 2: { + stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); + stbtt_uint8 *rangeArray = coverageTable + 4; + + // Binary search. + stbtt_int32 l=0, r=rangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(rangeRecord); + strawEnd = ttUSHORT(rangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else { + stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + break; + } + + default: return -1; // unsupported + } + + return -1; +} + +static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) +{ + stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); + switch (classDefFormat) + { + case 1: { + stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); + stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); + stbtt_uint8 *classDef1ValueArray = classDefTable + 6; + + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); + break; + } + + case 2: { + stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); + stbtt_uint8 *classRangeRecords = classDefTable + 4; + + // Binary search. + stbtt_int32 l=0, r=classRangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(classRangeRecord); + strawEnd = ttUSHORT(classRangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else + return (stbtt_int32)ttUSHORT(classRangeRecord + 4); + } + break; + } + + default: + return -1; // Unsupported definition type, return an error. + } + + // "All glyphs not assigned to a class fall into class 0". (OpenType spec) + return 0; +} + +// Define to STBTT_assert(x) if you want to break on unimplemented formats. +#define STBTT_GPOS_TODO_assert(x) + +static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint16 lookupListOffset; + stbtt_uint8 *lookupList; + stbtt_uint16 lookupCount; + stbtt_uint8 *data; + stbtt_int32 i, sti; + + if (!info->gpos) return 0; + + data = info->data + info->gpos; + + if (ttUSHORT(data+0) != 1) return 0; // Major version 1 + if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 + + lookupListOffset = ttUSHORT(data+8); + lookupList = data + lookupListOffset; + lookupCount = ttUSHORT(lookupList); + + for (i=0; i= pairSetCount) return 0; + + needle=glyph2; + r=pairValueCount-1; + l=0; + + // Binary search. + while (l <= r) { + stbtt_uint16 secondGlyph; + stbtt_uint8 *pairValue; + m = (l + r) >> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(pairValue); + straw = secondGlyph; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + stbtt_int16 xAdvance = ttSHORT(pairValue + 2); + return xAdvance; + } + } + } else + return 0; + break; + } + + case 2: { + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + if (valueFormat1 == 4 && valueFormat2 == 0) { // Support more formats? + stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); + stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); + int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); + int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + + stbtt_uint16 class1Count = ttUSHORT(table + 12); + stbtt_uint16 class2Count = ttUSHORT(table + 14); + stbtt_uint8 *class1Records, *class2Records; + stbtt_int16 xAdvance; + + if (glyph1class < 0 || glyph1class >= class1Count) return 0; // malformed + if (glyph2class < 0 || glyph2class >= class2Count) return 0; // malformed + + class1Records = table + 16; + class2Records = class1Records + 2 * (glyph1class * class2Count); + xAdvance = ttSHORT(class2Records + 2 * glyph2class); + return xAdvance; + } else + return 0; + break; + } + + default: + return 0; // Unsupported position format + } + } + } + + return 0; +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) +{ + int xAdvance = 0; + + if (info->gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + else if (info->kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + return xAdvance; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) +{ + int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); + if (!tab) + return 0; + if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); + if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); + if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); + return 1; +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl) +{ + int i; + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info); + + int numEntries = ttUSHORT(svg_doc_list); + stbtt_uint8 *svg_docs = svg_doc_list + 2; + + for(i=0; i= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2))) + return svg_doc; + } + return 0; +} + +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg) +{ + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc; + + if (info->svg == 0) + return 0; + + svg_doc = stbtt_FindSVGDoc(info, gl); + if (svg_doc != NULL) { + *svg = (char *) data + info->svg + ttULONG(svg_doc + 4); + return ttULONG(svg_doc + 8); + } else { + return 0; + } +} + +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg) +{ + return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static float stbtt__sized_trapezoid_area(float height, float top_width, float bottom_width) +{ + STBTT_assert(top_width >= 0); + STBTT_assert(bottom_width >= 0); + return (top_width + bottom_width) / 2.0f * height; +} + +static float stbtt__position_trapezoid_area(float height, float tx0, float tx1, float bx0, float bx1) +{ + return stbtt__sized_trapezoid_area(height, tx1 - tx0, bx1 - bx0); +} + +static float stbtt__sized_triangle_area(float height, float width) +{ + return height * width / 2; +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = (sy1 - sy0) * e->direction; + STBTT_assert(x >= 0 && x < len); + scanline[x] += stbtt__position_trapezoid_area(height, x_top, x+1.0f, x_bottom, x+1.0f); + scanline_fill[x] += height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, y_final, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + STBTT_assert(dy >= 0); + STBTT_assert(dx >= 0); + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = y_top + dy * (x1+1 - x0); + + // compute intersection with y axis at x2 + y_final = y_top + dy * (x2 - x0); + + // x1 x_top x2 x_bottom + // y_top +------|-----+------------+------------+--------|---+------------+ + // | | | | | | + // | | | | | | + // sy0 | Txxxxx|............|............|............|............| + // y_crossing | *xxxxx.......|............|............|............| + // | | xxxxx..|............|............|............| + // | | /- xx*xxxx........|............|............| + // | | dy < | xxxxxx..|............|............| + // y_final | | \- | xx*xxx.........|............| + // sy1 | | | | xxxxxB...|............| + // | | | | | | + // | | | | | | + // y_bottom +------------+------------+------------+------------+------------+ + // + // goal is to measure the area covered by '.' in each pixel + + // if x2 is right at the right edge of x1, y_crossing can blow up, github #1057 + // @TODO: maybe test against sy1 rather than y_bottom? + if (y_crossing > y_bottom) + y_crossing = y_bottom; + + sign = e->direction; + + // area of the rectangle covered from sy0..y_crossing + area = sign * (y_crossing-sy0); + + // area of the triangle (x_top,sy0), (x1+1,sy0), (x1+1,y_crossing) + scanline[x1] += stbtt__sized_triangle_area(area, x1+1 - x_top); + + // check if final y_crossing is blown up; no test case for this + if (y_final > y_bottom) { + y_final = y_bottom; + dy = (y_final - y_crossing ) / (x2 - (x1+1)); // if denom=0, y_final = y_crossing, so y_final <= y_bottom + } + + // in second pixel, area covered by line segment found in first pixel + // is always a rectangle 1 wide * the height of that line segment; this + // is exactly what the variable 'area' stores. it also gets a contribution + // from the line segment within it. the THIRD pixel will get the first + // pixel's rectangle contribution, the second pixel's rectangle contribution, + // and its own contribution. the 'own contribution' is the same in every pixel except + // the leftmost and rightmost, a trapezoid that slides down in each pixel. + // the second pixel's contribution to the third pixel will be the + // rectangle 1 wide times the height change in the second pixel, which is dy. + + step = sign * dy * 1; // dy is dy/dx, change in y for every 1 change in x, + // which multiplied by 1-pixel-width is how much pixel area changes for each step in x + // so the area advances by 'step' every time + + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; // area of trapezoid is 1*step/2 + area += step; + } + STBTT_assert(STBTT_fabs(area) <= 1.01f); // accumulated error from area += step unless we round step down + STBTT_assert(sy1 > y_final-0.01f); + + // area covered in the last pixel is the rectangle from all the pixels to the left, + // plus the trapezoid filled by the line segment in this pixel all the way to the right edge + scanline[x2] += area + sign * stbtt__position_trapezoid_area(sy1-y_final, (float) x2, x2+1.0f, x_bottom, x2+1.0f); + + // the rest of the line is filled based on the total height of the line segment in this pixel + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + // note though that this does happen some of the time because + // x_top and x_bottom can be extrapolated at the top & bottom of + // the shape and actually lie outside the bounding box + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + if (j == 0 && off_y != 0) { + if (z->ey < scan_y_top) { + // this can happen due to subpixel positioning and some kind of fp rounding error i think + z->ey = scan_y_top; + } + } + STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count = 0; + int *winding_lengths = NULL; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + spc->skip_missing = 0; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) +{ + spc->skip_missing = skip; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + int missing_glyph_added = 0; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) { + rects[k].w = rects[k].h = 0; + } else { + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + if (glyph == 0) + missing_glyph_added = 1; + } + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, missing_glyph = -1, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed && r->w != 0 && r->h != 0) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + + if (glyph == 0) + missing_glyph = j; + } else if (spc->skip_missing) { + return_value = 0; + } else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) { + ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph]; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) +{ + int i_ascent, i_descent, i_lineGap; + float scale; + stbtt_fontinfo info; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); + scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); + stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); + *ascent = (float) i_ascent * scale; + *descent = (float) i_descent * scale; + *lineGap = (float) i_lineGap * scale; +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float) STBTT_sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int stbtt__equal(float *a, float *b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float orig[2], ray[2] = { 1, 0 }; + float y_frac; + int winding = 0; + + // make sure y never passes through a vertex of the shape + y_frac = (float) STBTT_fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + + orig[0] = x; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i=0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; + int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; + int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; + int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float q0[2],q1[2],q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (stbtt__equal(q0,q1) || stbtt__equal(q1,q2)) { + x0 = (int)verts[i-1].x; + y0 = (int)verts[i-1].y; + x1 = (int)verts[i ].x; + y1 = (int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -(float) STBTT_pow(-x,1.0f/3.0f); + else + return (float) STBTT_pow( x,1.0f/3.0f); +} + +// x^3 + a*x^2 + b*x + c = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = (float) STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float) STBTT_sqrt(-p/3); + float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative + float m = (float) STBTT_cos(v); + float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? + //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); + //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + unsigned char *data; + + if (scale == 0) return NULL; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + // if empty, return NULL + if (ix0 == ix1 || iy0 == iy1) + return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 != 0.0f) + precompute[i] = 1.0f / (bx*bx + by*by); + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float) x + 0.5f; + float sy = (float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + if (verts[i].type == STBTT_vline && precompute[i] != 0.0f) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + float dist,dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + // coarse culling against bbox + //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) + dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy + // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + // coarse culling against bbox to avoid computing cubic unnecessarily + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3] = {0.f,0.f,0.f}; + float px,py,t,it,dist2; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (a == 0.0) { // if a is 0, it's linear + if (b != 0.0) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = (float) STBTT_sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; // don't bother distinguishing 1-solution case, as code below will still work + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.25 (2021-07-11) many fixes +// 1.24 (2020-02-05) fix warning +// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) +// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined +// 1.21 (2019-02-25) fix warning +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/Minecraft.Client/ThirdParty/vui.h b/Minecraft.Client/ThirdParty/vui.h new file mode 100644 index 0000000..9962ddf --- /dev/null +++ b/Minecraft.Client/ThirdParty/vui.h @@ -0,0 +1,425 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace vui { + +struct Color { + uint8_t r, g, b, a; + + static Color rgb(uint8_t r, uint8_t g, uint8_t b) { return {r, g, b, 255}; } + static Color rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { return {r, g, b, a}; } +}; + +static constexpr Color White = {255, 255, 255, 255}; +static constexpr Color Black = {0, 0, 0, 255}; +static constexpr Color Red = {255, 0, 0, 255}; +static constexpr Color Green = {0, 255, 0, 255}; +static constexpr Color Blue = {0, 0, 255, 255}; +static constexpr Color Yellow = {255, 255, 0, 255}; + +inline float lerp(float a, float b, float t) { return a + (b - a) * t; } +inline float easeIn(float t) { return t * t; } +inline float easeOut(float t) { return t * (2.0f - t); } +inline float easeInOut(float t) { + return t < 0.5f ? 2.0f * t * t : -1.0f + (4.0f - 2.0f * t) * t; +} + +struct Rect { float x, y, w, h; }; + +enum class Alignment { Left, Center, Right }; + +enum class Anchor { + None, + TopLeft, TopCenter, TopRight, + CenterLeft, Center, CenterRight, + BottomLeft, BottomCenter, BottomRight +}; + +struct ButtonStyle { + int normalTex = -1, hoverTex = -1, pressedTex = -1; + int font = 0; + Color textColor = White; + Color hoverTextColor = Yellow; + Color shadowColor = {15, 15, 15, 255}; + float shadowOffset = 2; + float width = 450, height = 40; +}; + +struct TextStyle { + int font = 0; + Color color = White; + Color shadowColor = {0, 0, 0, 0}; + float shadowOffset = 0; + Alignment align = Alignment::Left; +}; + +struct PanelStyle { + int backgroundTex = -1; + Color backgroundColor = {0, 0, 0, 0}; + float padding = 0; + bool nineSlice = false; + float sliceInsets[4] = {}; +}; + +struct SliderStyle { + int trackTex = -1, fillTex = -1, thumbTex = -1, bgTex = -1; + int font = 0; + Color textColor = White; + float width = 300, height = 40; +}; + +struct CheckboxStyle { + int bgTex = -1, hoverTex = -1, checkTex = -1; + int font = 0; + Color textColor = White; + float size = 24; +}; + +struct TextInputStyle { + int bgTex = -1, borderTex = -1, activeBorderTex = -1; + int font = 0; + Color textColor = White; + Color placeholderColor = {128, 128, 128, 255}; + float width = 300, height = 40; +}; + +class Renderer { +public: + Renderer(); + ~Renderer(); + + Renderer(const Renderer &) = delete; + Renderer &operator=(const Renderer &) = delete; + + void init(VkDevice device, VkPhysicalDevice physDevice, + VkQueue graphicsQueue, uint32_t queueFamily, + VkFormat swapchainFormat, uint32_t imageCount); + void shutdown(); + + void beginFrame(int windowW, int windowH, int framebufferW = 0, int framebufferH = 0); + void endFrame(VkCommandBuffer cmd, uint32_t imageIndex); + void setOverlayFramebuffers(VkImageView *views, uint32_t count, uint32_t width, uint32_t height); + VkRenderPass getRenderPass() const; + + int loadFont(const char *path, float size); + int loadFontFromMemory(const void *data, int dataSize, float size); + int loadTexture(const char *path); + int createTexture(int width, int height, const void *rgba); + void updateTexture(int id, int x, int y, int w, int h, const void *rgba); + void destroyTexture(int id); + + void drawRect(float x, float y, float w, float h, Color color); + void drawRectOutline(float x, float y, float w, float h, float thickness, Color color); + void drawImage(int textureId, float x, float y, float w, float h, Color tint = White); + void drawImageRegion(int textureId, float x, float y, float w, float h, + float u0, float v0, float u1, float v1, Color tint = White); + void drawImage9Slice(int textureId, float x, float y, float w, float h, + float borderLeft, float borderRight, float borderTop, float borderBottom, + int texW, int texH, Color tint = White); + void drawText(const char *text, float x, float y, Color color, int fontId = 0); + void drawTextCentered(const char *text, float x, float y, float w, float h, + Color color, int fontId = 0); + void drawTextWrapped(const char *text, float x, float y, float maxWidth, + Color color, int fontId = 0); + + float measureText(const char *text, int fontId = 0); + float fontHeight(int fontId = 0); + + void pushScissor(float x, float y, float w, float h); + void popScissor(); + void pushTransform(); + void popTransform(); + void translate(float x, float y); + void rotate(float angleDeg); + void scale(float sx, float sy); + + void feedMousePosition(float x, float y); + void feedMouseButton(int button, bool pressed); + void feedMouseScroll(float dx, float dy); + void feedKey(int key, bool pressed); + void feedChar(unsigned int codepoint); + + bool isMouseOver(float x, float y, float w, float h); + bool isMouseClicked(float x, float y, float w, float h); + bool isMouseDown(float x, float y, float w, float h); + bool isKeyPressed(int key); + bool isKeyClicked(int key); + bool isMouseDownAnywhere(); + bool isMouseClickedAnywhere(); + float mouseX(); + float mouseY(); + float scrollDeltaX(); + float scrollDeltaY(); + int pollChar(); + + int windowW() const; + int windowH() const; + int textureWidth(int id) const; + int textureHeight(int id) const; + +private: + struct Impl; + Impl *impl; +}; + +class Element { +public: + Element(); + virtual ~Element(); + Element(Element &&other) noexcept; + Element &operator=(Element &&other) noexcept; + Element(const Element &) = delete; + Element &operator=(const Element &) = delete; + + float posX = 0, posY = 0; + float w = 0, h = 0; + float rotation = 0; + float scaleX = 1, scaleY = 1; + float pivotX = 0, pivotY = 0; + float alpha = 1.0f; + bool visible = true; + bool focused = false; + bool focusable = false; + Anchor anchor = Anchor::None; + Element *anchorTarget = nullptr; + std::string name; + Element *parent = nullptr; + std::vector> children; + + std::function onUpdate; + + Element *addChild(std::unique_ptr child); + Element *findChild(const std::string &searchName) const; + + void updateTree(float dt); + void renderTree(Renderer &r); + void handleInputTree(Renderer &r); + + virtual void render(Renderer &r); + virtual void handleInput(Renderer &r); + + float worldX() const; + float worldY() const; + +protected: + bool hovered_ = false; + bool isPointInside(Renderer &r) const; +}; + +class Image : public Element { +public: + int texture = -1; + int texW = 0, texH = 0; + Color tint = White; + bool preserveAspect = false; + bool useRegion = false; + float u0 = 0, v0 = 0, u1 = 1, v1 = 1; + bool nineSlice = false; + float sliceInsets[4] = {}; + + void render(Renderer &r) override; +}; + +class Text : public Element { +public: + std::string text; + TextStyle style; + bool wrap = false; + + void render(Renderer &r) override; +}; + +class Panel : public Element { +public: + PanelStyle style; + + void render(Renderer &r) override; + void renderTree(Renderer &r); +}; + +class Button : public Element { +public: + Button() { focusable = true; } + std::string label; + ButtonStyle style; + std::function onClick; + + void render(Renderer &r) override; + void handleInput(Renderer &r) override; + +private: + bool pressed_ = false; +}; + +class Slider : public Element { +public: + Slider() { focusable = true; } + SliderStyle style; + std::string label; + float minVal = 0, maxVal = 1, value = 0.5f; + std::function onChange; + + void render(Renderer &r) override; + void handleInput(Renderer &r) override; + +private: + bool dragging_ = false; +}; + +class Checkbox : public Element { +public: + Checkbox() { focusable = true; } + CheckboxStyle style; + bool checked = false; + std::string label; + std::function onChange; + + void render(Renderer &r) override; + void handleInput(Renderer &r) override; +}; + +class TextInput : public Element { +public: + TextInputStyle style; + std::string text; + std::string placeholder; + int maxLength = 256; + bool active = false; + int cursorPos = 0; + std::function onChange; + + void render(Renderer &r) override; + void handleInput(Renderer &r) override; +}; + +class ScrollList : public Element { +public: + struct Item { + std::string label; + int tag = 0; + }; + + std::vector items; + float scrollOffset = 0; + int visibleCount = 8; + int selectedIndex = -1; + float itemHeight = 30; + int font = 0; + Color textColor = White; + Color selectedColor = {80, 80, 140, 255}; + Color hoverColor = {50, 50, 90, 255}; + Color bgColor = {0, 0, 0, 0}; + std::function onSelect; + + void render(Renderer &r) override; + void handleInput(Renderer &r) override; + +private: + int hoveredIndex_ = -1; +}; + +class VStack : public Element { +public: + float spacing = 0; + + void render(Renderer &r) override; + + template + T &add(std::unique_ptr child) { + T *ptr = child.get(); + addChild(std::move(child)); + return *ptr; + } + + Button &button(const std::string &lbl, const ButtonStyle &btnStyle, + std::function clickFn); + Text &text(const std::string &str, const TextStyle &textStyle); + Slider &slider(const SliderStyle &sliderStyle, float min, float max, float val, + std::function changeFn); + Checkbox &checkbox(const std::string &lbl, const CheckboxStyle &cbStyle, bool isChecked, + std::function changeFn); + Image &image(int tex, float width, float height); +}; + +class HStack : public Element { +public: + float spacing = 0; + + void render(Renderer &r) override; + + template + T &add(std::unique_ptr child) { + T *ptr = child.get(); + addChild(std::move(child)); + return *ptr; + } + + Button &button(const std::string &lbl, const ButtonStyle &btnStyle, + std::function clickFn); + Text &text(const std::string &str, const TextStyle &textStyle); + Image &image(int tex, float width, float height); +}; + +class Scene { +public: + Scene(); + explicit Scene(const std::string &sceneName); + + std::string name; + float baseW = 1280, baseH = 720; + Element root; + std::function onBack; + int focusIndex = -1; + + void render(Renderer &r); + void handleInput(Renderer &r); + Element *findElement(const std::string &searchName) const; + + Image &background(int tex); + Image &image(int tex, Rect bounds); + Text &text(const std::string &str, Rect bounds, const TextStyle &textStyle); + Panel &panel(Rect bounds, const PanelStyle &panelStyle); + Button &button(const std::string &lbl, Rect bounds, const ButtonStyle &btnStyle, + std::function clickFn); + VStack &vstack(float x, float y, float sp); + HStack &hstack(float x, float y, float sp); + Slider &slider(Rect bounds, const SliderStyle &sliderStyle, + float min, float max, float val, + std::function changeFn); + Checkbox &checkbox(const std::string &lbl, float x, float y, + const CheckboxStyle &cbStyle, bool isChecked, + std::function changeFn); + TextInput &textInput(Rect bounds, const TextInputStyle &inputStyle, + const std::string &placeholderText, + std::function changeFn); + ScrollList &scrollList(Rect bounds, int visCount, float iHeight); +}; + +class SceneManager { +public: + void push(std::unique_ptr scene); + void push(Scene &&scene); + std::unique_ptr pop(); + Scene *top() const; + bool empty() const; + int depth() const; + + void render(Renderer &r); + void handleInput(Renderer &r); + +private: + void flushDeferred(); + std::vector> stack_; + int pendingPops_ = 0; + std::vector> pendingPushes_; + bool insideHandleInput_ = false; +}; + +} // namespace vui diff --git a/Minecraft.Client/ThirdParty/vui_elements.cpp b/Minecraft.Client/ThirdParty/vui_elements.cpp new file mode 100644 index 0000000..adfafbe --- /dev/null +++ b/Minecraft.Client/ThirdParty/vui_elements.cpp @@ -0,0 +1,895 @@ +#include "vui.h" +#include +#include + +namespace vui { + +Element::Element() = default; +Element::~Element() = default; + +Element::Element(Element &&other) noexcept + : posX(other.posX), posY(other.posY), w(other.w), h(other.h), + alpha(other.alpha), visible(other.visible), + name(std::move(other.name)), parent(other.parent), + children(std::move(other.children)), hovered_(other.hovered_) { + for (auto &c : children) + c->parent = this; + other.parent = nullptr; +} + +Element &Element::operator=(Element &&other) noexcept { + if (this != &other) { + posX = other.posX; + posY = other.posY; + w = other.w; + h = other.h; + alpha = other.alpha; + visible = other.visible; + name = std::move(other.name); + parent = other.parent; + children = std::move(other.children); + hovered_ = other.hovered_; + for (auto &c : children) + c->parent = this; + other.parent = nullptr; + } + return *this; +} + +Element *Element::addChild(std::unique_ptr child) { + child->parent = this; + auto *ptr = child.get(); + children.push_back(std::move(child)); + return ptr; +} + +Element *Element::findChild(const std::string &searchName) const { + for (auto &c : children) { + if (c->name == searchName) return c.get(); + Element *found = c->findChild(searchName); + if (found) return found; + } + return nullptr; +} + +float Element::worldX() const { + float wx = posX; + if (anchorTarget && anchor != Anchor::None) { + float tx = anchorTarget->worldX(); + float tw = anchorTarget->w; + switch (anchor) { + case Anchor::TopLeft: case Anchor::CenterLeft: case Anchor::BottomLeft: + wx += tx; break; + case Anchor::TopCenter: case Anchor::Center: case Anchor::BottomCenter: + wx += tx + tw * 0.5f; break; + case Anchor::TopRight: case Anchor::CenterRight: case Anchor::BottomRight: + wx += tx + tw; break; + default: break; + } + } else { + for (Element *p = parent; p; p = p->parent) + wx += p->posX; + } + return wx; +} + +float Element::worldY() const { + float wy = posY; + if (anchorTarget && anchor != Anchor::None) { + float ty = anchorTarget->worldY(); + float th = anchorTarget->h; + switch (anchor) { + case Anchor::TopLeft: case Anchor::TopCenter: case Anchor::TopRight: + wy += ty; break; + case Anchor::CenterLeft: case Anchor::Center: case Anchor::CenterRight: + wy += ty + th * 0.5f; break; + case Anchor::BottomLeft: case Anchor::BottomCenter: case Anchor::BottomRight: + wy += ty + th; break; + default: break; + } + } else { + for (Element *p = parent; p; p = p->parent) + wy += p->posY; + } + return wy; +} + +bool Element::isPointInside(Renderer &r) const { + float wx = worldX(); + float wy = worldY(); + return r.isMouseOver(wx, wy, w, h); +} + +void Element::updateTree(float dt) { + if (onUpdate) onUpdate(*this, dt); + for (auto &child : children) + child->updateTree(dt); +} + +void Element::renderTree(Renderer &r) { + if (!visible) return; + r.pushTransform(); + r.translate(posX, posY); + if (rotation != 0 || scaleX != 1 || scaleY != 1) { + r.translate(pivotX, pivotY); + if (rotation != 0) r.rotate(rotation); + if (scaleX != 1 || scaleY != 1) r.scale(scaleX, scaleY); + r.translate(-pivotX, -pivotY); + } + render(r); + for (auto &child : children) + child->renderTree(r); + r.popTransform(); +} + +void Element::handleInputTree(Renderer &r) { + if (!visible) return; + for (int i = static_cast(children.size()) - 1; i >= 0; --i) + children[i]->handleInputTree(r); + handleInput(r); +} + +void Element::render(Renderer &) {} +void Element::handleInput(Renderer &) {} + +void Image::render(Renderer &r) { + if (texture < 0) return; + Color t = {tint.r, tint.g, tint.b, static_cast(tint.a * alpha)}; + + float drawX = 0, drawY = 0, drawW = w, drawH = h; + + if (preserveAspect) { + int tw = texW > 0 ? texW : r.textureWidth(texture); + int th = texH > 0 ? texH : r.textureHeight(texture); + if (tw > 0 && th > 0) { + float imgAR = (float)tw / (float)th; + float boxAR = w / h; + if (imgAR > boxAR) { + drawW = w; + drawH = w / imgAR; + } else { + drawH = h; + drawW = h * imgAR; + } + drawX = (w - drawW) * 0.5f; + drawY = (h - drawH) * 0.5f; + } + } + + if (nineSlice) { + int sw = texW > 0 ? texW : r.textureWidth(texture); + int sh = texH > 0 ? texH : r.textureHeight(texture); + r.drawImage9Slice(texture, drawX, drawY, drawW, drawH, + sliceInsets[0], sliceInsets[1], sliceInsets[2], sliceInsets[3], + sw, sh, t); + } else if (useRegion) { + r.drawImageRegion(texture, drawX, drawY, drawW, drawH, u0, v0, u1, v1, t); + } else { + r.drawImage(texture, drawX, drawY, drawW, drawH, t); + } +} + +void Text::render(Renderer &r) { + if (text.empty()) return; + Color col = {style.color.r, style.color.g, style.color.b, + static_cast(style.color.a * alpha)}; + Color shadow = {style.shadowColor.r, style.shadowColor.g, style.shadowColor.b, + static_cast(style.shadowColor.a * alpha)}; + + float drawX = 0; + if (!wrap) { + if (style.align == Alignment::Center) { + float tw = r.measureText(text.c_str(), style.font); + drawX = (w - tw) * 0.5f; + } else if (style.align == Alignment::Right) { + float tw = r.measureText(text.c_str(), style.font); + drawX = w - tw; + } + } + + if (wrap) { + if (style.shadowOffset > 0 && shadow.a > 0) + r.drawTextWrapped(text.c_str(), drawX + style.shadowOffset, style.shadowOffset, + w, shadow, style.font); + r.drawTextWrapped(text.c_str(), drawX, 0, w, col, style.font); + } else { + if (style.shadowOffset > 0 && shadow.a > 0) + r.drawText(text.c_str(), drawX + style.shadowOffset, style.shadowOffset, + shadow, style.font); + r.drawText(text.c_str(), drawX, 0, col, style.font); + } +} + +void Panel::render(Renderer &r) { + if (style.backgroundTex >= 0) { + Color t = {255, 255, 255, static_cast(255 * alpha)}; + if (style.nineSlice) { + int sw = r.textureWidth(style.backgroundTex); + int sh = r.textureHeight(style.backgroundTex); + r.drawImage9Slice(style.backgroundTex, 0, 0, w, h, + style.sliceInsets[0], style.sliceInsets[1], + style.sliceInsets[2], style.sliceInsets[3], + sw, sh, t); + } else { + r.drawImage(style.backgroundTex, 0, 0, w, h, t); + } + } else if (style.backgroundColor.a > 0) { + Color col = {style.backgroundColor.r, style.backgroundColor.g, style.backgroundColor.b, + static_cast(style.backgroundColor.a * alpha)}; + r.drawRect(0, 0, w, h, col); + } +} + +void Panel::renderTree(Renderer &r) { + if (!visible) return; + r.pushTransform(); + r.translate(posX, posY); + render(r); + r.pushTransform(); + r.translate(style.padding, style.padding); + for (auto &child : children) + child->renderTree(r); + r.popTransform(); + r.popTransform(); +} + +void Button::render(Renderer &r) { + float wx = worldX(); + float wy = worldY(); + bool down = r.isMouseDown(wx, wy, w, h); + + int tex = style.normalTex; + if (down && style.pressedTex >= 0) + tex = style.pressedTex; + else if (hovered_ && style.hoverTex >= 0) + tex = style.hoverTex; + + if (tex >= 0) { + Color t = {255, 255, 255, static_cast(255 * alpha)}; + r.drawImage(tex, 0, 0, w, h, t); + } + + if (!label.empty()) { + Color textCol = hovered_ ? style.hoverTextColor : style.textColor; + Color col = {textCol.r, textCol.g, textCol.b, + static_cast(textCol.a * alpha)}; + + if (style.shadowOffset > 0 && style.shadowColor.a > 0) { + Color sc = {style.shadowColor.r, style.shadowColor.g, style.shadowColor.b, + static_cast(style.shadowColor.a * alpha)}; + r.drawTextCentered(label.c_str(), style.shadowOffset, style.shadowOffset, + w, h, sc, style.font); + } + + r.drawTextCentered(label.c_str(), 0, 0, w, h, col, style.font); + } +} + +void Button::handleInput(Renderer &r) { + hovered_ = isPointInside(r) || focused; + + float wx = worldX(); + float wy = worldY(); + if (r.isMouseClicked(wx, wy, w, h) && onClick) + onClick(); + if (focused && r.isKeyClicked(257) && onClick) + onClick(); +} + +void Slider::render(Renderer &r) { + Color t = {255, 255, 255, static_cast(255 * alpha)}; + float range = maxVal - minVal; + float norm = (range > 0) ? (value - minVal) / range : 0; + + if (style.bgTex >= 0) + r.drawImage(style.bgTex, 0, 0, w, h, t); + + if (style.trackTex >= 0) + r.drawImage(style.trackTex, 0, 0, w, h, t); + + float fillW = w * norm; + if (style.fillTex >= 0 && fillW > 0) + r.drawImage(style.fillTex, 0, 0, fillW, h, t); + + if (style.thumbTex >= 0) { + float thumbSize = h; + float thumbX = fillW - thumbSize * 0.5f; + thumbX = std::max(0.0f, std::min(thumbX, w - thumbSize)); + r.drawImage(style.thumbTex, thumbX, 0, thumbSize, thumbSize, t); + } + + if (!label.empty()) { + Color shadow = {15, 15, 15, t.a}; + float textY = (h - r.fontHeight(style.font)) * 0.5f; + r.drawTextCentered(label.c_str(), 1, textY + 1, w, r.fontHeight(style.font), shadow, style.font); + r.drawTextCentered(label.c_str(), 0, textY, w, r.fontHeight(style.font), style.textColor, style.font); + } +} + +void Slider::handleInput(Renderer &r) { + float wx = worldX(); + float wy = worldY(); + bool over = r.isMouseOver(wx, wy, w, h); + + if (r.isMouseClickedAnywhere() && over) + dragging_ = true; + + if (!r.isMouseDownAnywhere()) + dragging_ = false; + + if (dragging_) { + float localX = r.mouseX() - wx; + localX = std::max(0.0f, std::min(w, localX)); + float range = maxVal - minVal; + float newVal = minVal + (localX / w) * range; + newVal = std::max(minVal, std::min(maxVal, newVal)); + if (newVal != value) { + value = newVal; + if (onChange) onChange(value); + } + } +} + +void Checkbox::render(Renderer &r) { + Color t = {255, 255, 255, static_cast(255 * alpha)}; + float boxSize = style.size; + + int bgId = (hovered_ && style.hoverTex >= 0) ? style.hoverTex : style.bgTex; + if (bgId >= 0) + r.drawImage(bgId, 0, 0, boxSize, boxSize, t); + + if (checked && style.checkTex >= 0) + r.drawImage(style.checkTex, 0, 0, boxSize, boxSize, t); + + if (!label.empty()) { + Color col = {style.textColor.r, style.textColor.g, style.textColor.b, + static_cast(style.textColor.a * alpha)}; + float textX = boxSize + 8.0f; + float textY = (boxSize - r.fontHeight(style.font)) * 0.5f; + r.drawText(label.c_str(), textX, textY, col, style.font); + } +} + +void Checkbox::handleInput(Renderer &r) { + float wx = worldX(); + float wy = worldY(); + hovered_ = r.isMouseOver(wx, wy, w, h) || focused; + + if (r.isMouseClicked(wx, wy, w, h) || (focused && r.isKeyClicked(257))) { + checked = !checked; + if (onChange) onChange(checked); + } +} + +void TextInput::render(Renderer &r) { + Color t = {255, 255, 255, static_cast(255 * alpha)}; + + if (style.bgTex >= 0) + r.drawImage(style.bgTex, 0, 0, w, h, t); + + int borderId = active ? style.activeBorderTex : style.borderTex; + if (borderId >= 0) + r.drawImage(borderId, 0, 0, w, h, t); + + float textY = (h - r.fontHeight(style.font)) * 0.5f; + float pad = 6.0f; + + r.pushScissor(worldX() + pad, worldY(), w - pad * 2, h); + + if (text.empty() && !active && !placeholder.empty()) { + Color pc = {style.placeholderColor.r, style.placeholderColor.g, + style.placeholderColor.b, + static_cast(style.placeholderColor.a * alpha)}; + r.drawText(placeholder.c_str(), pad, textY, pc, style.font); + } else if (!text.empty()) { + Color tc = {style.textColor.r, style.textColor.g, style.textColor.b, + static_cast(style.textColor.a * alpha)}; + r.drawText(text.c_str(), pad, textY, tc, style.font); + } + + if (active) { + std::string beforeCursor = text.substr(0, cursorPos); + float cursorX = pad + r.measureText(beforeCursor.c_str(), style.font); + Color cc = {style.textColor.r, style.textColor.g, style.textColor.b, + static_cast(style.textColor.a * alpha)}; + r.drawRect(cursorX, textY, 1.5f, r.fontHeight(style.font), cc); + } + + r.popScissor(); +} + +void TextInput::handleInput(Renderer &r) { + float wx = worldX(); + float wy = worldY(); + + if (r.isMouseClickedAnywhere()) { + active = r.isMouseOver(wx, wy, w, h); + if (active) + cursorPos = static_cast(text.size()); + } + + if (!active) return; + + int ch; + while ((ch = r.pollChar()) >= 0) { + if (ch >= 32 && static_cast(text.size()) < maxLength) { + text.insert(text.begin() + cursorPos, static_cast(ch)); + cursorPos++; + if (onChange) onChange(text); + } + } + + if (r.isKeyClicked(259)) { + if (cursorPos > 0 && !text.empty()) { + text.erase(text.begin() + cursorPos - 1); + cursorPos--; + if (onChange) onChange(text); + } + } + + if (r.isKeyClicked(262)) { + if (cursorPos < static_cast(text.size())) + cursorPos++; + } + + if (r.isKeyClicked(263)) { + if (cursorPos > 0) + cursorPos--; + } +} + +void ScrollList::render(Renderer &r) { + if (bgColor.a > 0) { + Color bg = {bgColor.r, bgColor.g, bgColor.b, + static_cast(bgColor.a * alpha)}; + r.drawRect(0, 0, w, h, bg); + } + + r.pushScissor(worldX(), worldY(), w, h); + + int startIdx = static_cast(scrollOffset / itemHeight); + if (startIdx < 0) startIdx = 0; + int endIdx = startIdx + visibleCount + 1; + if (endIdx > static_cast(items.size())) endIdx = static_cast(items.size()); + + for (int i = startIdx; i < endIdx; i++) { + float iy = i * itemHeight - scrollOffset; + + if (i == selectedIndex) { + Color sc = {selectedColor.r, selectedColor.g, selectedColor.b, + static_cast(selectedColor.a * alpha)}; + r.drawRect(0, iy, w, itemHeight, sc); + } else if (i == hoveredIndex_) { + Color hc = {hoverColor.r, hoverColor.g, hoverColor.b, + static_cast(hoverColor.a * alpha)}; + r.drawRect(0, iy, w, itemHeight, hc); + } + + Color tc = {textColor.r, textColor.g, textColor.b, + static_cast(textColor.a * alpha)}; + float textY = iy + (itemHeight - r.fontHeight(font)) * 0.5f; + r.drawText(items[i].label.c_str(), 8.0f, textY, tc, font); + } + + r.popScissor(); +} + +void ScrollList::handleInput(Renderer &r) { + float wx = worldX(); + float wy = worldY(); + bool over = r.isMouseOver(wx, wy, w, h); + + hoveredIndex_ = -1; + if (over) { + float localY = r.mouseY() - wy + scrollOffset; + int idx = static_cast(localY / itemHeight); + if (idx >= 0 && idx < static_cast(items.size())) + hoveredIndex_ = idx; + + float dy = r.scrollDeltaY(); + if (dy != 0) { + scrollOffset -= dy * itemHeight * 0.5f; + float maxScroll = std::max(0.0f, static_cast(items.size()) * itemHeight - h); + scrollOffset = std::max(0.0f, std::min(maxScroll, scrollOffset)); + } + } + + if (over && r.isMouseClickedAnywhere() && hoveredIndex_ >= 0) { + selectedIndex = hoveredIndex_; + if (onSelect) onSelect(selectedIndex); + } + + if (over && r.isKeyClicked(265) && selectedIndex > 0) { + selectedIndex--; + if (onSelect) onSelect(selectedIndex); + float top = selectedIndex * itemHeight; + if (top < scrollOffset) scrollOffset = top; + } + + if (over && r.isKeyClicked(264) && selectedIndex < static_cast(items.size()) - 1) { + selectedIndex++; + if (onSelect) onSelect(selectedIndex); + float bottom = (selectedIndex + 1) * itemHeight; + if (bottom > scrollOffset + h) scrollOffset = bottom - h; + } +} + +void VStack::render(Renderer &) { + float cy = 0; + for (auto &child : children) { + child->posY = cy; + cy += child->h + spacing; + } +} + +Button &VStack::button(const std::string &lbl, const ButtonStyle &btnStyle, + std::function clickFn) { + auto b = std::make_unique