Merge pull request #59 from neocturne/sign-colors

Sign color fixes
This commit is contained in:
Matthias Schiffer 2025-01-06 21:24:46 +01:00 committed by GitHub
commit 0a3f6d7765
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 195 additions and 28 deletions

View file

@ -2,8 +2,21 @@
## [Unreleased] - ReleaseDate ## [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 ## [2.3.0] - 2025-01-02
### Added
- Added support for Minecraft 1.21.4 block types - Added support for Minecraft 1.21.4 block types
- Added support for Minecraft 1.21.4 Pale Garden biome - Added support for Minecraft 1.21.4 Pale Garden biome
- viewer: added images for pale oak signs - viewer: added images for pale oak signs

64
Cargo.lock generated
View file

@ -584,6 +584,7 @@ dependencies = [
"minedmap-types", "minedmap-types",
"num-integer", "num-integer",
"num_cpus", "num_cpus",
"phf",
"rayon", "rayon",
"regex", "regex",
"rustc-hash", "rustc-hash",
@ -717,6 +718,48 @@ dependencies = [
"windows-targets", "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]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.15" version = "0.2.15"
@ -766,6 +809,21 @@ dependencies = [
"proc-macro2", "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]] [[package]]
name = "rayon" name = "rayon"
version = "1.10.0" version = "1.10.0"
@ -923,6 +981,12 @@ version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "siphasher"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.9" version = "0.4.9"

View file

@ -52,6 +52,7 @@ minedmap-resource = { version = "0.5.0", path = "crates/resource" }
minedmap-types = { version = "0.1.2", path = "crates/types" } minedmap-types = { version = "0.1.2", path = "crates/types" }
num-integer = "0.1.45" num-integer = "0.1.45"
num_cpus = "1.16.0" num_cpus = "1.16.0"
phf = { version = "0.11.2", features = ["macros"] }
rayon = "1.7.0" rayon = "1.7.0"
regex = "1.10.2" regex = "1.10.2"
rustc-hash = "2.0.0" rustc-hash = "2.0.0"

View file

@ -38,7 +38,7 @@ pub enum BlockFlag {
} }
/// An RGB color with u8 components /// 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]); pub struct Color(pub [u8; 3]);
/// An RGB color with f32 components /// An RGB color with f32 components

View file

@ -46,7 +46,7 @@ pub const MIPMAP_FILE_META_VERSION: FileMetaVersion = FileMetaVersion(0);
/// MinedMap processed entity data version number /// MinedMap processed entity data version number
/// ///
/// Increase when entity collection changes bacause of code changes. /// 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 /// Coordinate pair of a generated tile
/// ///

View file

@ -1,7 +1,8 @@
//! Newtype and helper methods for handling Minecraft Raw JSON Text //! 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}; use serde::{Deserialize, Serialize};
/// A span of formatted text /// A span of formatted text
@ -17,8 +18,8 @@ pub struct FormattedText {
/// Text content /// Text content
pub text: String, pub text: String,
/// Text color /// Text color
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none", with = "json_color")]
pub color: Option<Arc<String>>, pub color: Option<Color>,
/// Bold formatting /// Bold formatting
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub bold: Option<bool>, pub bold: Option<bool>,
@ -41,7 +42,7 @@ impl FormattedText {
pub fn inherit(self, parent: &Self) -> Self { pub fn inherit(self, parent: &Self) -> Self {
FormattedText { FormattedText {
text: self.text, text: self.text,
color: self.color.or_else(|| parent.color.clone()), color: self.color.or(parent.color),
bold: self.bold.or(parent.bold), bold: self.bold.or(parent.bold),
italic: self.italic.or(parent.italic), italic: self.italic.or(parent.italic),
underlined: self.underlined.or(parent.underlined), underlined: self.underlined.or(parent.underlined),
@ -175,3 +176,83 @@ impl JSONText {
serde_json::from_str(&self.0).unwrap_or_default() 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<S>(color: &Option<Color>, serializer: S) -> Result<S::Ok, S::Error>
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<Color>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a string representing a color")
}
fn visit_str<E>(self, color: &str) -> Result<Self::Value, E>
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<Option<Color>, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(ColorVisitor)
}
}

View file

@ -1,7 +1,8 @@
//! Processing of sign text //! Processing of sign text
use std::{fmt::Display, sync::Arc}; use std::fmt::Display;
use minedmap_resource::Color;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::{ use super::{
@ -23,10 +24,34 @@ pub struct RawSignText<'a> {
pub color: Option<&'a str>, 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<'_> { impl RawSignText<'_> {
/// Decodes the [RawSignText] into a [SignText] /// Decodes the [RawSignText] into a [SignText]
pub fn decode(&self) -> 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 { let parent = FormattedText {
color, color,
..Default::default() ..Default::default()

View file

@ -153,25 +153,6 @@ const parseHash = function () {
return args; 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) { function formatSignLine(line) {
const el = document.createElement('span'); const el = document.createElement('span');
el.style.whiteSpace = 'pre'; el.style.whiteSpace = 'pre';
@ -180,7 +161,9 @@ function formatSignLine(line) {
const child = document.createElement('span'); const child = document.createElement('span');
child.textContent = span.text; child.textContent = span.text;
const color = colors[span.color ?? 'black'] || colors['black']; let color = span.color ?? '';
if (color[0] !== '#')
color = '#000000';
if (span.bold) if (span.bold)
child.style.fontWeight = 'bold'; child.style.fontWeight = 'bold';