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
### 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
- 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

64
Cargo.lock generated
View file

@ -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"

View file

@ -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"

View file

@ -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

View file

@ -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
///

View file

@ -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<Arc<String>>,
#[serde(skip_serializing_if = "Option::is_none", with = "json_color")]
pub color: Option<Color>,
/// Bold formatting
#[serde(skip_serializing_if = "Option::is_none")]
pub bold: Option<bool>,
@ -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<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
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()

View file

@ -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';