Compare commits

...

2 Commits

Author SHA1 Message Date
Matt Mascarenhas 14dafa4abe cinera.c: Fix stack-use-after-return segfault
The ClashResolver, or a memory_book variable therein, triggered a
stack-use-after-return segfault. Fixed by initialising the ClashResolver
in main() and passing it to InitClashResolver() by pointer, rather than
initialising it in InitClashResolver().
2024-03-12 13:03:08 +00:00
Matt Mascarenhas 213bb2f882 cinera.c: Fix colouring of topic dots
The colour computed for topic dots and put into cinera_topics.css in HSL
format has its lightness value modified depending on whether it's on a
dark or light background. Web browsers do not tell us an element's
computed colour in HSL format, but in RGB, so we would need to convert
it from RGB to HSL when setting the lightness. Previously, this
conversion was busted, calculating too small a value for the saturation.

This commit obviates the need for any RGB→HSL conversion by writing the
hue and saturation values as attributes to the elements, which the
function responsible for setting the lightness may use directly.
2024-02-21 20:52:34 +00:00
2 changed files with 163 additions and 133 deletions

View File

@ -23,7 +23,7 @@ typedef struct
version CINERA_APP_VERSION = {
.Major = 0,
.Minor = 10,
.Patch = 28
.Patch = 30
};
#define __USE_XOPEN2K8 // NOTE(matt): O_NOFOLLOW
@ -903,8 +903,8 @@ FreeBook(memory_book *M)
void
FreeAndReinitialiseBook(memory_book *M)
{
int PageSize = M->PageSize;
int DataWidthInBytes = M->DataWidthInBytes;
uint64_t PageSize = M->PageSize;
uint64_t DataWidthInBytes = M->DataWidthInBytes;
FreeBook(M);
@ -4172,6 +4172,68 @@ LogEdit(edit_type_id EditType, string Lineage, string EntryID, string *EntryTitl
}
}
#define CINERA_HSL_TRANSPARENT_HUE 65535
typedef struct
{
unsigned int Hue:16;
unsigned int Saturation:8;
unsigned int Lightness:8;
} hsl_colour;
hsl_colour
CharToColour(char Char)
{
hsl_colour Colour;
if(Char >= 'a' && Char <= 'z')
{
Colour.Hue = (((float)Char - 'a') / ('z' - 'a') * 360);
Colour.Saturation = (((float)Char - 'a') / ('z' - 'a') * 26 + 74);
}
else if(Char >= 'A' && Char <= 'Z')
{
Colour.Hue = (((float)Char - 'A') / ('Z' - 'A') * 360);
Colour.Saturation = (((float)Char - 'A') / ('Z' - 'A') * 26 + 74);
}
else if(Char >= '0' && Char <= '9')
{
Colour.Hue = (((float)Char - '0') / ('9' - '0') * 360);
Colour.Saturation = (((float)Char - '0') / ('9' - '0') * 26 + 74);
}
else
{
Colour.Hue = 180;
Colour.Saturation = 50;
}
return Colour;
}
void
StringToColourHash(hsl_colour *Colour, string String)
{
Colour->Hue = 0;
Colour->Saturation = 0;
Colour->Lightness = 74;
for(int i = 0; i < String.Length; ++i)
{
Colour->Hue += CharToColour(String.Base[i]).Hue;
Colour->Saturation += CharToColour(String.Base[i]).Saturation;
}
Colour->Hue = Colour->Hue % 360;
Colour->Saturation = Colour->Saturation % 26 + 74;
}
bool
IsValidHSLColour(hsl_colour Colour)
{
return (Colour.Hue >= 0 && Colour.Hue < 360)
&& (Colour.Saturation >= 0 && Colour.Saturation <= 100)
&& (Colour.Lightness >= 0 && Colour.Lightness <= 100);
}
#define SLASH 1
#define NULLTERM 1
typedef struct
@ -4326,6 +4388,7 @@ typedef struct
{
string Marker;
string WrittenText;
hsl_colour Colour;
} category_info;
#define CopyString(Dest, DestSize, Format, ...) CopyString_(__LINE__, (Dest), (DestSize), (Format), ##__VA_ARGS__)
@ -4720,18 +4783,16 @@ typedef struct
bool Resolving;
} clash_resolver;
clash_resolver
InitClashResolver(void)
void
InitClashResolver(clash_resolver *ClashResolver)
{
clash_resolver Result = {};
Result.Book[0] = InitBook(sizeof(clash_entry), 8);
Result.Book[1] = InitBook(sizeof(clash_entry), 8);
Result.Main = &Result.Book[0];
Result.Holder = &Result.Book[1];
Result.Chain = InitBookOfPointers(8);
Result.ChainStructure = CS_OPEN_ENDED;
Result.Resolving = FALSE;
return Result;
ClashResolver->Book[0] = InitBook(sizeof(clash_entry), 8);
ClashResolver->Book[1] = InitBook(sizeof(clash_entry), 8);
ClashResolver->Main = &ClashResolver->Book[0];
ClashResolver->Holder = &ClashResolver->Book[1];
ClashResolver->Chain = InitBookOfPointers(8);
ClashResolver->ChainStructure = CS_OPEN_ENDED;
ClashResolver->Resolving = FALSE;
}
void
@ -5481,58 +5542,6 @@ TimecodeToDottedSeconds(v4 Timecode)
return (float)Timecode.Hours * SECONDS_PER_HOUR + (float)Timecode.Minutes * SECONDS_PER_MINUTE + (float)Timecode.Seconds + (float)Timecode.Milliseconds / 1000;
}
typedef struct
{
unsigned int Hue:16;
unsigned int Saturation:8;
unsigned int Lightness:8;
} hsl_colour;
hsl_colour
CharToColour(char Char)
{
hsl_colour Colour;
if(Char >= 'a' && Char <= 'z')
{
Colour.Hue = (((float)Char - 'a') / ('z' - 'a') * 360);
Colour.Saturation = (((float)Char - 'a') / ('z' - 'a') * 26 + 74);
}
else if(Char >= 'A' && Char <= 'Z')
{
Colour.Hue = (((float)Char - 'A') / ('Z' - 'A') * 360);
Colour.Saturation = (((float)Char - 'A') / ('Z' - 'A') * 26 + 74);
}
else if(Char >= '0' && Char <= '9')
{
Colour.Hue = (((float)Char - '0') / ('9' - '0') * 360);
Colour.Saturation = (((float)Char - '0') / ('9' - '0') * 26 + 74);
}
else
{
Colour.Hue = 180;
Colour.Saturation = 50;
}
return Colour;
}
void
StringToColourHash(hsl_colour *Colour, string String)
{
Colour->Hue = 0;
Colour->Saturation = 0;
Colour->Lightness = 74;
for(int i = 0; i < String.Length; ++i)
{
Colour->Hue += CharToColour(String.Base[i]).Hue;
Colour->Saturation += CharToColour(String.Base[i]).Saturation;
}
Colour->Hue = Colour->Hue % 360;
Colour->Saturation = Colour->Saturation % 26 + 74;
}
char *
SanitisePunctuation(char *String)
{
@ -8102,7 +8111,7 @@ BuildCredits(string HMMLFilepath, buffer *CreditsMenu, HMML_VideoMetaData *Metad
}
void
InsertCategory(_memory_book(category_info) *GlobalTopics, _memory_book(category_info) *LocalTopics, _memory_book(category_info) *GlobalMedia, _memory_book(category_info) *LocalMedia, string Marker)
InsertCategory(_memory_book(category_info) *GlobalTopics, _memory_book(category_info) *LocalTopics, _memory_book(category_info) *GlobalMedia, _memory_book(category_info) *LocalMedia, string Marker, hsl_colour *Colour)
{
medium *Medium = GetMediumFromProject(CurrentProject, Marker);
@ -8203,11 +8212,15 @@ InsertCategory(_memory_book(category_info) *GlobalTopics, _memory_book(category_
{
category_info *Src = GetPlaceInBook(LocalTopics, CategoryCount - 1);
category_info *Dest = GetPlaceInBook(LocalTopics, CategoryCount);
Dest->Marker = Src->Marker;
*Dest = *Src;
}
category_info *New = GetPlaceInBook(LocalTopics, CategoryCount);
New->Marker = Marker;
if(Colour)
{
New->Colour = *Colour;
}
break;
}
}
@ -8218,6 +8231,10 @@ InsertCategory(_memory_book(category_info) *GlobalTopics, _memory_book(category_
{
category_info *New = GetPlaceInBook(LocalTopics, TopicIndex);
New->Marker = Marker;
if(Colour)
{
New->Colour = *Colour;
}
}
bool MadeGlobalSpace = FALSE;
@ -8241,11 +8258,15 @@ InsertCategory(_memory_book(category_info) *GlobalTopics, _memory_book(category_
{
category_info *Src = GetPlaceInBook(GlobalTopics, CategoryCount - 1);
category_info *Dest = GetPlaceInBook(GlobalTopics, CategoryCount);
Dest->Marker = Src->Marker;
*Dest = *Src;
}
category_info *New = GetPlaceInBook(GlobalTopics, CategoryCount);
New->Marker = Marker;
if(Colour)
{
New->Colour = *Colour;
}
break;
}
}
@ -8257,6 +8278,10 @@ InsertCategory(_memory_book(category_info) *GlobalTopics, _memory_book(category_
{
category_info *New = GetPlaceInBook(GlobalTopics, TopicIndex);
New->Marker = Marker;
if(Colour)
{
New->Colour = *Colour;
}
}
}
}
@ -8348,9 +8373,18 @@ BuildCategoryIcons(buffer *CategoryIcons, _memory_book(category_info) *LocalTopi
CopyString(SanitisedMarker, sizeof(SanitisedMarker), "%.*s", (int)This->Marker.Length, This->Marker.Base);
SanitisePunctuation(SanitisedMarker);
CopyStringToBuffer(CategoryIcons, "<div title=\"%.*s\" class=\"category %s\"></div>",
CopyStringToBuffer(CategoryIcons,
"<div title=\"%.*s\" class=\"category %s\"",
(int)This->Marker.Length, This->Marker.Base,
SanitisedMarker);
if(IsValidHSLColour(This->Colour))
{
CopyStringToBuffer(CategoryIcons,
" data-hue=\"%u\" data-saturation=\"%u%%\"",
This->Colour.Hue, This->Colour.Saturation);
}
CopyStringToBuffer(CategoryIcons,
"></div>");
}
}
@ -8630,7 +8664,7 @@ BuildQuote(memory_book *Strings, quote_info *Info, string Speaker, int ID, bool
}
rc
GenerateTopicColours(neighbourhood *N, string Topic)
GenerateTopicColours(neighbourhood *N, string Topic, hsl_colour *Dest)
{
rc Result = RC_SUCCESS;
// NOTE(matt): Stack-string
@ -8641,6 +8675,15 @@ GenerateTopicColours(neighbourhood *N, string Topic)
medium *Medium = GetMediumFromProject(CurrentProject, Topic);
if(!Medium)
{
if(StringsMatch(Topic, Wrap0("nullTopic")))
{
Dest->Hue = CINERA_HSL_TRANSPARENT_HUE;
}
else
{
StringToColourHash(Dest, Topic);
}
file Topics = {};
Topics.Path = 0;
Topics.Buffer.ID = BID_TOPICS;
@ -8706,10 +8749,8 @@ GenerateTopicColours(neighbourhood *N, string Topic)
}
else
{
hsl_colour Colour;
StringToColourHash(&Colour, Topic);
WriteToFile(Topics.Handle, ".category.%s { border: 1px solid hsl(%d, %d%%, %d%%); background: hsl(%d, %d%%, %d%%); }\n",
SanitisedTopic, Colour.Hue, Colour.Saturation, Colour.Lightness, Colour.Hue, Colour.Saturation, Colour.Lightness);
SanitisedTopic, Dest->Hue, Dest->Saturation, Dest->Lightness, Dest->Hue, Dest->Saturation, Dest->Lightness);
}
#if DEBUG_MEM
@ -10899,7 +10940,7 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m
{
*HasFilterMenu = TRUE;
}
InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0("authored"));
InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0("authored"), NULL);
hsl_colour AuthorColour;
StringToColourHash(&AuthorColour, Author);
// TODO(matt): That EDITION_NETWORK site database API-polling stuff
@ -10924,14 +10965,15 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m
HMML_MarkerType Type = Timestamp->markers[MarkerIndex].type;
if(Type == HMML_CATEGORY)
{
Result = GenerateTopicColours(N, Wrap0(Timestamp->markers[MarkerIndex].marker));
hsl_colour TopicColour = {};
Result = GenerateTopicColours(N, Wrap0(Timestamp->markers[MarkerIndex].marker), &TopicColour);
if(Result == RC_SUCCESS)
{
if(!*HasFilterMenu)
{
*HasFilterMenu = TRUE;
}
InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0(Timestamp->markers[MarkerIndex].marker));
InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0(Timestamp->markers[MarkerIndex].marker), &TopicColour);
CopyStringToBuffer(&IndexBuffers->Text, "%.*s", (int)StringLength(Readable), InPtr);
}
else
@ -11162,14 +11204,15 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m
while(MarkerIndex < Timestamp->marker_count)
{
Result = GenerateTopicColours(N, Wrap0(Timestamp->markers[MarkerIndex].marker));
hsl_colour TopicColour = {};
Result = GenerateTopicColours(N, Wrap0(Timestamp->markers[MarkerIndex].marker), &TopicColour);
if(Result == RC_SUCCESS)
{
if(!*HasFilterMenu)
{
*HasFilterMenu = TRUE;
}
InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0(Timestamp->markers[MarkerIndex].marker));
InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0(Timestamp->markers[MarkerIndex].marker), &TopicColour);
++MarkerIndex;
}
else
@ -11182,10 +11225,11 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m
{
if(LocalTopics.ItemCount == 0)
{
Result = GenerateTopicColours(N, Wrap0("nullTopic"));
hsl_colour TopicColour = {};
Result = GenerateTopicColours(N, Wrap0("nullTopic"), &TopicColour);
if(Result == RC_SUCCESS)
{
InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0("nullTopic"));
InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0("nullTopic"), &TopicColour);
}
}
@ -11193,7 +11237,7 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m
{
if(LocalMedia.ItemCount == 0)
{
InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, DefaultMedium->ID);
InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, DefaultMedium->ID, NULL);
}
BuildTimestampClass(&IndexBuffers->Class, &LocalTopics, &LocalMedia, DefaultMedium->ID);
@ -11991,13 +12035,21 @@ HMMLToBuffers(buffers *CollationBuffers, template *BespokeTemplate, string BaseF
bool NullTopic = StringsMatch(This->Marker, Wrap0("nullTopic"));
CopyStringToBuffer(&MenuBuffers.FilterTopics,
" <div %sclass=\"filter_content %s\">\n"
" <span class=\"icon category %s\"></span><span class=\"cineraText\">%.*s</span>\n"
" </div>\n",
" <div%s class=\"filter_content %s\">\n"
" <span class=\"icon category %s\"",
NullTopic ? "title=\"Timestamps that don't fit into the above topic(s) may be filtered using this pseudo-topic\" " : "",
SanitisedMarker,
NullTopic ? " title=\"Timestamps that don't fit into the above topic(s) may be filtered using this pseudo-topic\"" : "",
SanitisedMarker,
SanitisedMarker);
if(IsValidHSLColour(This->Colour))
{
CopyStringToBuffer(&MenuBuffers.FilterTopics,
" data-hue=\"%u\" data-saturation=\"%u%%\"",
This->Colour.Hue, This->Colour.Saturation);
}
CopyStringToBuffer(&MenuBuffers.FilterTopics,
"></span><span class=\"cineraText\">%.*s</span>\n"
" </div>\n",
NullTopic ? (int)sizeof("(null topic)")-1 : (int)This->Marker.Length,
NullTopic ? "(null topic)" : This->Marker.Base);
}
@ -18271,7 +18323,8 @@ main(int ArgC, char **Args)
CollationBuffers.Search.ID = BID_COLLATION_BUFFERS_SEARCH; // NOTE(matt): Allocated by SearchToBuffer()
memory_book TokensList = InitBook(sizeof(tokens), 8);
clash_resolver ClashResolver = InitClashResolver();
clash_resolver ClashResolver = {};
InitClashResolver(&ClashResolver);
template BespokeTemplate = {};
neighbourhood Neighbourhood = {};

View File

@ -551,35 +551,6 @@ BindHelp(Button, DocumentationContainer)
})
}
function RGBtoHSL(colour)
{
var rgb = colour.slice(4, -1).split(", ");
var red = rgb[0];
var green = rgb[1];
var blue = rgb[2];
var min = Math.min(red, green, blue);
var max = Math.max(red, green, blue);
var chroma = max - min;
var hue = 0;
if(max == red)
{
hue = ((green - blue) / chroma) % 6;
}
else if(max == green)
{
hue = ((blue - red) / chroma) + 2;
}
else if(max == blue)
{
hue = ((red - green) / chroma) + 4;
}
var saturation = chroma / 255 * 100;
hue = (hue * 60) < 0 ? 360 + (hue * 60) : (hue * 60);
return [hue, saturation]
}
function getBackgroundColourRGB(element) {
var Colour = getComputedStyle(element).getPropertyValue("background-color");
var depth = 0;
@ -610,28 +581,34 @@ function setTextLightness(textElement)
{
var textHue = textElement.getAttribute("data-hue");
var textSaturation = textElement.getAttribute("data-saturation");
if(getBackgroundBrightness(textElement.parentNode) < 127)
if(textHue && textSaturation)
{
textElement.style.color = ("hsl(" + textHue + ", " + textSaturation + ", 76%)");
}
else
{
textElement.style.color = ("hsl(" + textHue + ", " + textSaturation + ", 24%)");
if(getBackgroundBrightness(textElement.parentNode) < 127)
{
textElement.style.color = ("hsl(" + textHue + ", " + textSaturation + ", 76%)");
}
else
{
textElement.style.color = ("hsl(" + textHue + ", " + textSaturation + ", 24%)");
}
}
}
function setDotLightness(topicDot)
{
var Hue = RGBtoHSL(getComputedStyle(topicDot).getPropertyValue("background-color"))[0];
var Saturation = RGBtoHSL(getComputedStyle(topicDot).getPropertyValue("background-color"))[1];
if(getBackgroundBrightness(topicDot.parentNode) < 127)
var dotHue = topicDot.getAttribute("data-hue");
var dotSaturation = topicDot.getAttribute("data-saturation");
if(dotHue && dotSaturation)
{
topicDot.style.backgroundColor = ("hsl(" + Hue + ", " + Saturation + "%, 76%)");
topicDot.style.borderColor = ("hsl(" + Hue + ", " + Saturation + "%, 76%)");
}
else
{
topicDot.style.backgroundColor = ("hsl(" + Hue + ", " + Saturation + "%, 47%)");
topicDot.style.borderColor = ("hsl(" + Hue + ", " + Saturation + "%, 47%)");
if(getBackgroundBrightness(topicDot.parentNode) < 127)
{
topicDot.style.backgroundColor = ("hsl(" + dotHue + ", " + dotSaturation + ", 76%)");
topicDot.style.borderColor = ("hsl(" + dotHue + ", " + dotSaturation + ", 76%)");
}
else
{
topicDot.style.backgroundColor = ("hsl(" + dotHue + ", " + dotSaturation + ", 47%)");
topicDot.style.borderColor = ("hsl(" + dotHue + ", " + dotSaturation + ", 47%)");
}
}
}