From 7f329ac8e73ef7f7d13ff842026b5397b7fa8f0a Mon Sep 17 00:00:00 2001 From: Matthias Schiffer Date: Mon, 6 Jan 2025 20:30:17 +0100 Subject: [PATCH 1/3] CHANGELOG.md: fix heading for previous release --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab33e93..4c9847e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ## [2.3.0] - 2025-01-02 +### Added + - Added support for Minecraft 1.21.4 block types - Added support for Minecraft 1.21.4 Pale Garden biome - viewer: added images for pale oak signs From 9375af8d54d13f40932579ba0e11fbabc0375bb8 Mon Sep 17 00:00:00 2001 From: Matthias Schiffer Date: Mon, 6 Jan 2025 20:27:01 +0100 Subject: [PATCH 2/3] resource: impl Ord for Color Allow using Color in FormattedText. --- crates/resource/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/resource/src/lib.rs b/crates/resource/src/lib.rs index 1afcc62..fed9514 100644 --- a/crates/resource/src/lib.rs +++ b/crates/resource/src/lib.rs @@ -38,7 +38,7 @@ pub enum BlockFlag { } /// An RGB color with u8 components -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct Color(pub [u8; 3]); /// An RGB color with f32 components From ff6e28d381574fecc0a10466faf35c60a9efd756 Mon Sep 17 00:00:00 2001 From: Matthias Schiffer Date: Mon, 6 Jan 2025 21:16:41 +0100 Subject: [PATCH 3/3] world, viewer: fix sign text colors - Fix text colors for signs modified using dye - Fix text colors specified using `#rrggbb` CSS syntax in JSON text Only named colors specified via JSON text were working as intended. Dyed signs use different color names. The mapping of color names to values is now handled by the generator. Both the generator and the viewer must be updated for sign text colors to work. --- CHANGELOG.md | 11 ++++++ Cargo.lock | 64 ++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/core/common.rs | 2 +- src/world/json_text.rs | 89 ++++++++++++++++++++++++++++++++++++++++-- src/world/sign.rs | 29 +++++++++++++- viewer/MinedMap.js | 23 ++--------- 7 files changed, 192 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c9847e..aabf1f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ ## [Unreleased] - ReleaseDate +### Fixed + +- Fix text colors for signs modified using dye +- Fix text colors specified using `#rrggbb` CSS syntax in JSON text + +Only named colors specified via JSON text were working as intended. Dyed signs use different +color names. + +The mapping of color names to values is now handled by the generator. Both the generator and the +viewer must be updated for sign text colors to work. + ## [2.3.0] - 2025-01-02 ### Added diff --git a/Cargo.lock b/Cargo.lock index c18929f..90a9b7a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -584,6 +584,7 @@ dependencies = [ "minedmap-types", "num-integer", "num_cpus", + "phf", "rayon", "regex", "rustc-hash", @@ -717,6 +718,48 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.15" @@ -766,6 +809,21 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + [[package]] name = "rayon" version = "1.10.0" @@ -923,6 +981,12 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" diff --git a/Cargo.toml b/Cargo.toml index e2f36b6..287ac4c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ minedmap-resource = { version = "0.5.0", path = "crates/resource" } minedmap-types = { version = "0.1.2", path = "crates/types" } num-integer = "0.1.45" num_cpus = "1.16.0" +phf = { version = "0.11.2", features = ["macros"] } rayon = "1.7.0" regex = "1.10.2" rustc-hash = "2.0.0" diff --git a/src/core/common.rs b/src/core/common.rs index 3dd01cf..be6d28a 100644 --- a/src/core/common.rs +++ b/src/core/common.rs @@ -46,7 +46,7 @@ pub const MIPMAP_FILE_META_VERSION: FileMetaVersion = FileMetaVersion(0); /// MinedMap processed entity data version number /// /// Increase when entity collection changes bacause of code changes. -pub const ENTITIES_FILE_META_VERSION: FileMetaVersion = FileMetaVersion(0); +pub const ENTITIES_FILE_META_VERSION: FileMetaVersion = FileMetaVersion(1); /// Coordinate pair of a generated tile /// diff --git a/src/world/json_text.rs b/src/world/json_text.rs index a153179..fa18527 100644 --- a/src/world/json_text.rs +++ b/src/world/json_text.rs @@ -1,7 +1,8 @@ //! Newtype and helper methods for handling Minecraft Raw JSON Text -use std::{collections::VecDeque, fmt::Display, sync::Arc}; +use std::{collections::VecDeque, fmt::Display}; +use minedmap_resource::Color; use serde::{Deserialize, Serialize}; /// A span of formatted text @@ -17,8 +18,8 @@ pub struct FormattedText { /// Text content pub text: String, /// Text color - #[serde(skip_serializing_if = "Option::is_none")] - pub color: Option>, + #[serde(skip_serializing_if = "Option::is_none", with = "json_color")] + pub color: Option, /// Bold formatting #[serde(skip_serializing_if = "Option::is_none")] pub bold: Option, @@ -41,7 +42,7 @@ impl FormattedText { pub fn inherit(self, parent: &Self) -> Self { FormattedText { text: self.text, - color: self.color.or_else(|| parent.color.clone()), + color: self.color.or(parent.color), bold: self.bold.or(parent.bold), italic: self.italic.or(parent.italic), underlined: self.underlined.or(parent.underlined), @@ -175,3 +176,83 @@ impl JSONText { serde_json::from_str(&self.0).unwrap_or_default() } } + +mod json_color { + //! Helpers for serializing and deserializing [FormattedText](super::FormattedText) colors + + use minedmap_resource::Color; + use serde::{ + de::{self, Visitor}, + ser::Error as _, + Deserializer, Serializer, + }; + + /// Named JSON text colors + static COLORS: phf::Map<&'static str, Color> = phf::phf_map! { + "black" => Color([0x00, 0x00, 0x00]), + "dark_blue" => Color([0x00, 0x00, 0xAA]), + "dark_green" => Color([0x00, 0xAA, 0x00]), + "dark_aqua" => Color([0x00, 0xAA, 0xAA]), + "dark_red" => Color([0xAA, 0x00, 0x00]), + "dark_purple" => Color([0xAA, 0x00, 0xAA]), + "gold" => Color([0xFF, 0xAA, 0x00]), + "gray" => Color([0xAA, 0xAA, 0xAA]), + "dark_gray" => Color([0x55, 0x55, 0x55]), + "blue" => Color([0x55, 0x55, 0xFF]), + "green" => Color([0x55, 0xFF, 0x55]), + "aqua" => Color([0x55, 0xFF, 0xFF]), + "red" => Color([0xFF, 0x55, 0x55]), + "light_purple" => Color([0xFF, 0x55, 0xFF]), + "yellow" => Color([0xFF, 0xFF, 0x55]), + "white" => Color([0xFF, 0xFF, 0xFF]), + }; + + /// serde serialize function for [FormattedText::color](super::FormattedText::color) + pub fn serialize(color: &Option, serializer: S) -> Result + where + S: Serializer, + { + let &Some(color) = color else { + return Err(S::Error::custom("serialize called for None sign color")); + }; + + let text = format!("#{:02x}{:02x}{:02x}", color.0[0], color.0[1], color.0[2]); + serializer.serialize_str(&text) + } + + /// serde [Visitor] for use by [deserialize] + struct ColorVisitor; + + impl Visitor<'_> for ColorVisitor { + type Value = Option; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a string representing a color") + } + + fn visit_str(self, color: &str) -> Result + where + E: de::Error, + { + if let Some(hex) = color.strip_prefix("#") { + if let Ok(value) = u32::from_str_radix(hex, 16) { + return Ok(Some(Color([ + (value >> 16) as u8, + (value >> 8) as u8, + value as u8, + ]))); + } + } + + Ok(COLORS.get(color).copied()) + } + } + + /// serde deserialize function for [FormattedText::color](super::FormattedText::color) + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + deserializer.deserialize_str(ColorVisitor) + } +} diff --git a/src/world/sign.rs b/src/world/sign.rs index 57b741a..eff319f 100644 --- a/src/world/sign.rs +++ b/src/world/sign.rs @@ -1,7 +1,8 @@ //! Processing of sign text -use std::{fmt::Display, sync::Arc}; +use std::fmt::Display; +use minedmap_resource::Color; use serde::{Deserialize, Serialize}; use super::{ @@ -23,10 +24,34 @@ pub struct RawSignText<'a> { pub color: Option<&'a str>, } +/// The color to use for signs without a color attribute ("black") +const DEFAULT_COLOR: Color = Color([0, 0, 0]); + +/// Map of text colors associated with dyes (except for black) +static DYE_COLORS: phf::Map<&'static str, Color> = phf::phf_map! { + "white" => Color([255, 255, 255]), + "orange" => Color([255, 104, 31]), + "magenta" => Color([255, 0, 255]), + "light_blue" => Color([154, 192, 205]), + "yellow" => Color([255, 255, 0]), + "lime" => Color([191, 255, 0]), + "pink" => Color([255, 105, 180]), + "gray" => Color([128, 128, 128]), + "light_gray" => Color([211, 211, 211]), + "cyan" => Color([0, 255, 255]), + "purple" => Color([160, 32, 240]), + "blue" => Color([0, 0, 255]), + "brown" => Color([139, 69, 19]), + "green" => Color([0, 255, 0]), + "red" => Color([255, 0, 0]), +}; + impl RawSignText<'_> { /// Decodes the [RawSignText] into a [SignText] pub fn decode(&self) -> SignText { - let color = self.color.map(|c| Arc::new(c.to_owned())); + let color = self + .color + .map(|c| DYE_COLORS.get(c).copied().unwrap_or(DEFAULT_COLOR)); let parent = FormattedText { color, ..Default::default() diff --git a/viewer/MinedMap.js b/viewer/MinedMap.js index e784eec..cfcccf1 100644 --- a/viewer/MinedMap.js +++ b/viewer/MinedMap.js @@ -153,25 +153,6 @@ const parseHash = function () { return args; } -const colors = { - black: '#000000', - dark_blue: '#0000AA', - dark_green: '#00AA00', - dark_aqua: '#00AAAA', - dark_red: '#AA0000', - dark_purple: '#AA00AA', - gold: '#FFAA00', - gray: '#AAAAAA', - dark_gray: '#555555', - blue: '#5555FF', - green: '#55FF55', - aqua: '#55FFFF', - red: '#FF5555', - light_purple: '#FF55FF', - yellow: '#FFFF55', - white: '#FFFFFF', -}; - function formatSignLine(line) { const el = document.createElement('span'); el.style.whiteSpace = 'pre'; @@ -180,7 +161,9 @@ function formatSignLine(line) { const child = document.createElement('span'); child.textContent = span.text; - const color = colors[span.color ?? 'black'] || colors['black']; + let color = span.color ?? ''; + if (color[0] !== '#') + color = '#000000'; if (span.bold) child.style.fontWeight = 'bold';