2323 lines
79 KiB
C
2323 lines
79 KiB
C
#if 0
|
|
ctime -begin ${0%.*}.ctm
|
|
gcc -g -Wall -fsanitize=address -std=c99 $0 -o ${0%.*} hmml.a
|
|
ctime -end ${0%.*}.ctm
|
|
exit
|
|
#endif
|
|
|
|
#define DEBUG 0
|
|
|
|
typedef unsigned int bool;
|
|
|
|
#define TRUE 1
|
|
#define FALSE 0
|
|
|
|
#include <stdarg.h> // NOTE(matt): varargs
|
|
#include <stdio.h> // NOTE(matt): printf, sprintf, vsprintf, fprintf, perror
|
|
#include <stdlib.h> // NOTE(matt): calloc, malloc, free
|
|
#include "hmmlib.h"
|
|
//#include "config.h" // TODO(matt): Implement config.h
|
|
|
|
#define Kilobytes(Bytes) Bytes << 10
|
|
#define Megabytes(Bytes) Bytes << 20
|
|
|
|
typedef struct
|
|
{
|
|
char *Location;
|
|
char *Ptr;
|
|
char *ID;
|
|
int Size;
|
|
} buffer;
|
|
|
|
// TODO(matt): Consider putting the ref_info and quote_info into linked lists on the heap, just to avoid all the hardcoded sizes
|
|
|
|
typedef struct
|
|
{
|
|
char Date[32];
|
|
char Text[512];
|
|
} quote_info;
|
|
|
|
typedef struct
|
|
{
|
|
char Timecode[8];
|
|
int Identifier;
|
|
} identifier;
|
|
|
|
#define REF_MAX_IDENTIFIER 32
|
|
|
|
typedef struct
|
|
{
|
|
char RefTitle[620];
|
|
char ID[512];
|
|
char URL[512];
|
|
char Source[256];
|
|
identifier Identifier[REF_MAX_IDENTIFIER];
|
|
int IdentifierCount;
|
|
} ref_info;
|
|
|
|
typedef struct
|
|
{
|
|
char Marker[32];
|
|
char WrittenText[32];
|
|
} category_info;
|
|
|
|
// TODO(matt): Parse this stuff out of a config file
|
|
char *Credentials[ ][5] =
|
|
{
|
|
{ "Miblo", "Matt Mascarenhas", "http://miblodelcarpio.co.uk", "patreon_logo.png", "https://patreon.com/miblo"},
|
|
{ "miotatsu", "Mio Iwakura", "http://riscy.tv/", "patreon_logo.png", "https://patreon.com/miotatsu"},
|
|
{ "nothings", "Sean Barrett", "https://nothings.org/", "", ""},
|
|
{ "cmuratori", "Casey Muratori", "https://handmadehero.org", "patreon_logo.png", "https://patreon.com/cmuratori"},
|
|
{ "fierydrake", "Mike Tunnicliffe", "", "", ""},
|
|
{ "abnercoimbre", "Abner Coimbre", "https://handmade.network/m/abnercoimbre", "patreon_logo.png", "https://patreon.com/handmade_dev"},
|
|
};
|
|
|
|
#define ArrayCount(A) sizeof(A)/sizeof(*(A))
|
|
|
|
void
|
|
ClaimBuffer(char *MemoryArena, int *ClaimedMemory, buffer *Buffer, char *ID, int Size)
|
|
{
|
|
Buffer->Location = MemoryArena + *ClaimedMemory;
|
|
Buffer->Size = Size;
|
|
Buffer->ID = ID;
|
|
*ClaimedMemory += Buffer->Size;
|
|
*Buffer->Location = '\0';
|
|
Buffer->Ptr = Buffer->Location;
|
|
#if DEBUG
|
|
printf(" Claimed: %s: %d\n"
|
|
" Total ClaimedMemory: %d\n\n", Buffer->ID, Buffer->Size, *ClaimedMemory);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
DeclaimBuffer(buffer *Buffer, int *ClaimedMemory)
|
|
{
|
|
*Buffer->Location = '\0';
|
|
*ClaimedMemory -= Buffer->Size;
|
|
#if DEBUG
|
|
printf("Declaimed: %s\n"
|
|
" Total ClaimedMemory: %d\n\n", Buffer->ID, *ClaimedMemory);
|
|
#endif
|
|
}
|
|
|
|
int
|
|
TimecodeToSeconds(char *Timecode)
|
|
{
|
|
int HMS[3] = { 0, 0, 0 }; // 0 == Seconds; 1 == Minutes; 2 == Hours
|
|
int Colons = 0;
|
|
while(*Timecode)
|
|
{
|
|
//if((*Timecode < '0' || *Timecode > '9') && *Timecode != ':') { return FALSE; }
|
|
|
|
if(*Timecode == ':')
|
|
{
|
|
++Colons;
|
|
//if(Colons > 2) { return FALSE; }
|
|
for(int i = 0; i < Colons; ++i)
|
|
{
|
|
HMS[Colons - i] = HMS[Colons - (i + 1)];
|
|
}
|
|
HMS[0] = 0;
|
|
}
|
|
else
|
|
{
|
|
HMS[0] = HMS[0] * 10 + *Timecode - '0';
|
|
}
|
|
|
|
++Timecode;
|
|
}
|
|
|
|
//if(HMS[0] > 59 || HMS[1] > 59 || Timecode[-1] == ':') { return FALSE; }
|
|
|
|
return HMS[2] * 60 * 60 + HMS[1] * 60 + HMS[0];
|
|
}
|
|
|
|
void
|
|
CopyBuffer(buffer *Dest, buffer *Src)
|
|
{
|
|
Src->Ptr = Src->Location;
|
|
while(*Src->Ptr)
|
|
{
|
|
// TODO(matt)
|
|
{
|
|
if(Dest->Ptr - Dest->Location >= Dest->Size)
|
|
{
|
|
fprintf(stderr, "CopyBuffer: %s cannot accommodate %s\n", Dest->ID, Src->ID);
|
|
__asm__("int3");
|
|
|
|
}
|
|
}
|
|
*Dest->Ptr++ = *Src->Ptr++;
|
|
}
|
|
}
|
|
|
|
__attribute__ ((format (printf, 2, 3)))
|
|
void
|
|
CopyString(char Dest[], char *Format, ...)
|
|
{
|
|
va_list Args;
|
|
va_start(Args, Format);
|
|
vsprintf(Dest, Format, Args);
|
|
va_end(Args);
|
|
}
|
|
|
|
void
|
|
CopyStringNoFormat(char *Dest, char *String)
|
|
{
|
|
while(*String)
|
|
{
|
|
*Dest++ = *String++;
|
|
}
|
|
}
|
|
|
|
int
|
|
StringLength(char *String)
|
|
{
|
|
int i = 0;
|
|
while(String[i])
|
|
{
|
|
++i;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
__attribute__ ((format (printf, 2, 3)))
|
|
void
|
|
CopyStringToBuffer(buffer *Dest, char *Format, ...)
|
|
{
|
|
va_list Args;
|
|
va_start(Args, Format);
|
|
int Length = vsnprintf(Dest->Ptr, Dest->Size - (Dest->Ptr - Dest->Location), Format, Args);
|
|
va_end(Args);
|
|
// TODO(matt):
|
|
{
|
|
if(Length + (Dest->Ptr - Dest->Location) >= Dest->Size)
|
|
{
|
|
fprintf(stderr, "CopyStringToBuffer: %s cannot accommodate %d-character string\n", Dest->ID, Length);
|
|
__asm__("int3");
|
|
}
|
|
}
|
|
Dest->Ptr += Length;
|
|
}
|
|
|
|
void
|
|
CopyStringToBufferHTMLSafe(buffer *Dest, char *String)
|
|
{
|
|
while(*String)
|
|
{
|
|
if(Dest->Ptr - Dest->Location >= Dest->Size)
|
|
{
|
|
fprintf(stderr, "CopyStringToBufferHTMLSafe: %s cannot accommodate %d-character string\n", Dest->ID, StringLength(String));
|
|
__asm__("int3");
|
|
}
|
|
switch(*String)
|
|
{
|
|
case '<':
|
|
CopyStringToBuffer(Dest, "<");
|
|
String++;
|
|
break;
|
|
case '>':
|
|
CopyStringToBuffer(Dest, ">");
|
|
String++;
|
|
break;
|
|
case '&':
|
|
CopyStringToBuffer(Dest, "&");
|
|
String++;
|
|
break;
|
|
case '\"':
|
|
CopyStringToBuffer(Dest, """);
|
|
String++;
|
|
break;
|
|
case '\'':
|
|
CopyStringToBuffer(Dest, "'");
|
|
String++;
|
|
break;
|
|
default:
|
|
*Dest->Ptr++ = *String++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
StringsDiffer(char *A, char *B)
|
|
{
|
|
while(*A && *B && *A == *B)
|
|
{
|
|
++A, ++B;
|
|
}
|
|
return *A - *B;
|
|
}
|
|
|
|
bool
|
|
StringsDifferL(char *A, char *B, int LengthofA)
|
|
{
|
|
int i = 0;
|
|
while(i < LengthofA && A[i] && A[i] == B[i])
|
|
{
|
|
++i;
|
|
}
|
|
if(!A[i] && LengthofA == i)
|
|
{
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
hsl_colour *
|
|
StringToColourHash(hsl_colour *Colour, char *String)
|
|
{
|
|
Colour->Hue = 0;
|
|
Colour->Saturation = 0;
|
|
Colour->Lightness = 26;
|
|
|
|
int i;
|
|
for(i = 0; String[i]; ++i)
|
|
{
|
|
Colour->Hue += CharToColour(String[i]).Hue;
|
|
Colour->Saturation += CharToColour(String[i]).Saturation;
|
|
}
|
|
|
|
Colour->Hue = Colour->Hue % 360;
|
|
Colour->Saturation = Colour->Saturation % 26 + 74;
|
|
return(Colour);
|
|
}
|
|
|
|
char *
|
|
SanitisePunctuation(char *String)
|
|
{
|
|
char *Ptr = String;
|
|
while(*Ptr)
|
|
{
|
|
if(*Ptr == ' ')
|
|
{
|
|
*Ptr = '_';
|
|
}
|
|
if((*Ptr < '0' || *Ptr > '9') &&
|
|
(*Ptr < 'a' || *Ptr > 'z') &&
|
|
(*Ptr < 'A' || *Ptr > 'Z'))
|
|
{
|
|
*Ptr = '-';
|
|
}
|
|
++Ptr;
|
|
}
|
|
return String;
|
|
}
|
|
|
|
int
|
|
BuildCredits(buffer *CreditsMenu, buffer *HostInfo, buffer *AnnotatorInfo, bool *HasCreditsMenu, char *Host, char *Annotator)
|
|
{
|
|
bool FoundHost = FALSE;
|
|
bool FoundAnnotator = FALSE;
|
|
|
|
for(int CredentialIndex = 0; CredentialIndex < ArrayCount(Credentials); ++CredentialIndex)
|
|
{
|
|
if(!StringsDiffer(Host, Credentials[CredentialIndex][0]))
|
|
{
|
|
FoundHost = TRUE; // TODO(matt): Check if this is actually necessary...
|
|
CopyStringToBuffer(HostInfo,
|
|
" <span class=\"credit\">\n");
|
|
if(*Credentials[CredentialIndex][2])
|
|
{
|
|
CopyStringToBuffer(HostInfo,
|
|
" <a class=\"person\" href=\"%s\" target=\"_blank\">\n"
|
|
" <div class=\"role\">Host</div>\n"
|
|
" <div class=\"name\">%s</div>\n"
|
|
" </a>\n",
|
|
Credentials[CredentialIndex][2],
|
|
Credentials[CredentialIndex][1]);
|
|
}
|
|
else
|
|
{
|
|
CopyStringToBuffer(HostInfo,
|
|
" <div class=\"person\">\n"
|
|
" <div class=\"role\">Host</div>\n"
|
|
" <div class=\"name\">%s</div>\n"
|
|
" </div>\n",
|
|
Credentials[CredentialIndex][1]);
|
|
}
|
|
|
|
if(*Credentials[CredentialIndex][4] && *Credentials[CredentialIndex][3])
|
|
{
|
|
CopyStringToBuffer(HostInfo,
|
|
" <a class=\"support\" href=\"%s\" target=\"_blank\"><img src=\"%s\"></a>\n",
|
|
Credentials[CredentialIndex][4],
|
|
Credentials[CredentialIndex][3]);
|
|
}
|
|
|
|
CopyStringToBuffer(HostInfo,
|
|
" </span>\n");
|
|
}
|
|
|
|
if(!StringsDiffer(Annotator, Credentials[CredentialIndex][0]))
|
|
{
|
|
FoundAnnotator = TRUE; // TODO(matt): Check if this is actually necessary...
|
|
CopyStringToBuffer(AnnotatorInfo,
|
|
" <span class=\"credit\">\n");
|
|
if(*Credentials[CredentialIndex][2])
|
|
{
|
|
CopyStringToBuffer(AnnotatorInfo,
|
|
" <a class=\"person\" href=\"%s\" target=\"_blank\">\n"
|
|
" <div class=\"role\">Annotator</div>\n"
|
|
" <div class=\"name\">%s</div>\n"
|
|
" </a>\n",
|
|
Credentials[CredentialIndex][2],
|
|
Credentials[CredentialIndex][1]);
|
|
}
|
|
else
|
|
{
|
|
CopyStringToBuffer(AnnotatorInfo,
|
|
" <div class=\"person\">\n"
|
|
" <div class=\"role\">Annotator</div>\n"
|
|
" <div class=\"name\">%s</div>\n"
|
|
" </div>\n",
|
|
Credentials[CredentialIndex][1]);
|
|
}
|
|
|
|
if(*Credentials[CredentialIndex][4] && *Credentials[CredentialIndex][3])
|
|
{
|
|
CopyStringToBuffer(AnnotatorInfo,
|
|
" <a class=\"support\" href=\"%s\" target=\"_blank\"><img src=\"%s\"></a>\n",
|
|
Credentials[CredentialIndex][4],
|
|
Credentials[CredentialIndex][3]);
|
|
}
|
|
|
|
CopyStringToBuffer(AnnotatorInfo,
|
|
" </span>\n");
|
|
}
|
|
}
|
|
|
|
if(FoundHost || FoundAnnotator)
|
|
{
|
|
CopyStringToBuffer(CreditsMenu,
|
|
" <div class=\"menu credits\">\n"
|
|
" <div class=\"mouse_catcher\"></div>\n"
|
|
" <span>Credits</span>\n"
|
|
" <div class=\"credits_container\">\n");
|
|
|
|
if(FoundHost)
|
|
{
|
|
CopyBuffer(CreditsMenu, HostInfo);
|
|
}
|
|
if(FoundAnnotator)
|
|
{
|
|
CopyBuffer(CreditsMenu, AnnotatorInfo);
|
|
}
|
|
CopyStringToBuffer(CreditsMenu,
|
|
" </div>\n"
|
|
" </div>\n");
|
|
}
|
|
else
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
*HasCreditsMenu = TRUE;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
BuildReference(ref_info *ReferencesArray, int RefIdentifier, int UniqueRefs, HMML_Reference Ref, HMML_Annotation Anno)
|
|
{
|
|
if(Ref.url && Ref.title && Ref.author && Ref.publisher && Ref.isbn)
|
|
{
|
|
CopyString(ReferencesArray[UniqueRefs].ID, Ref.isbn);
|
|
CopyString(ReferencesArray[UniqueRefs].Source, "%s (%s)", Ref.author, Ref.publisher);
|
|
CopyStringNoFormat(ReferencesArray[UniqueRefs].RefTitle, Ref.title);
|
|
CopyStringNoFormat(ReferencesArray[UniqueRefs].URL, Ref.url);
|
|
}
|
|
else if(Ref.page && Ref.url && Ref.title)
|
|
{
|
|
CopyStringNoFormat(ReferencesArray[UniqueRefs].ID, Ref.url);
|
|
CopyStringNoFormat(ReferencesArray[UniqueRefs].Source, Ref.title);
|
|
CopyStringNoFormat(ReferencesArray[UniqueRefs].RefTitle, Ref.page);
|
|
CopyStringNoFormat(ReferencesArray[UniqueRefs].URL, Ref.url);
|
|
}
|
|
else if(Ref.site && Ref.page && Ref.url)
|
|
{
|
|
CopyStringNoFormat(ReferencesArray[UniqueRefs].ID, Ref.url);
|
|
CopyStringNoFormat(ReferencesArray[UniqueRefs].Source, Ref.site);
|
|
CopyStringNoFormat(ReferencesArray[UniqueRefs].RefTitle, Ref.page);
|
|
CopyStringNoFormat(ReferencesArray[UniqueRefs].URL, Ref.url);
|
|
}
|
|
else if(Ref.site && Ref.url && Ref.title)
|
|
{
|
|
CopyStringNoFormat(ReferencesArray[UniqueRefs].ID, Ref.url);
|
|
CopyStringNoFormat(ReferencesArray[UniqueRefs].Source, Ref.site);
|
|
CopyStringNoFormat(ReferencesArray[UniqueRefs].RefTitle, Ref.title);
|
|
CopyStringNoFormat(ReferencesArray[UniqueRefs].URL, Ref.url);
|
|
}
|
|
else if(Ref.title && Ref.author && Ref.isbn)
|
|
{
|
|
CopyString(ReferencesArray[UniqueRefs].ID, Ref.isbn);
|
|
CopyString(ReferencesArray[UniqueRefs].Source, Ref.author);
|
|
CopyStringNoFormat(ReferencesArray[UniqueRefs].RefTitle, Ref.title);
|
|
//TODO(matt): Look into finding the best ISBN searcher the web has to offer
|
|
CopyString(ReferencesArray[UniqueRefs].URL, "http://www.openisbn.com/isbn/%s", Ref.isbn);
|
|
}
|
|
else if(Ref.url && Ref.article && Ref.author)
|
|
{
|
|
CopyStringNoFormat(ReferencesArray[UniqueRefs].ID, Ref.url);
|
|
CopyString(ReferencesArray[UniqueRefs].Source, Ref.author);
|
|
CopyStringNoFormat(ReferencesArray[UniqueRefs].RefTitle, Ref.article);
|
|
CopyStringNoFormat(ReferencesArray[UniqueRefs].URL, Ref.url);
|
|
}
|
|
else if(Ref.url && Ref.title && Ref.author)
|
|
{
|
|
CopyStringNoFormat(ReferencesArray[UniqueRefs].ID, Ref.url);
|
|
CopyString(ReferencesArray[UniqueRefs].Source, Ref.author);
|
|
CopyStringNoFormat(ReferencesArray[UniqueRefs].RefTitle, Ref.title);
|
|
CopyStringNoFormat(ReferencesArray[UniqueRefs].URL, Ref.url);
|
|
}
|
|
else if(Ref.url && Ref.title)
|
|
{
|
|
CopyStringNoFormat(ReferencesArray[UniqueRefs].ID, Ref.url);
|
|
CopyStringNoFormat(ReferencesArray[UniqueRefs].RefTitle, Ref.title);
|
|
CopyStringNoFormat(ReferencesArray[UniqueRefs].URL, Ref.url);
|
|
}
|
|
else if(Ref.site && Ref.url)
|
|
{
|
|
CopyStringNoFormat(ReferencesArray[UniqueRefs].ID, Ref.url);
|
|
CopyStringNoFormat(ReferencesArray[UniqueRefs].RefTitle, Ref.site);
|
|
CopyStringNoFormat(ReferencesArray[UniqueRefs].URL, Ref.url);
|
|
}
|
|
else
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
CopyString(ReferencesArray[UniqueRefs].Identifier[ReferencesArray[UniqueRefs].IdentifierCount].Timecode, Anno.time);
|
|
ReferencesArray[UniqueRefs].Identifier[ReferencesArray[UniqueRefs].IdentifierCount].Identifier = RefIdentifier;
|
|
return 0;
|
|
}
|
|
|
|
char *CategoryMedium[][3] =
|
|
{
|
|
// medium icon written name
|
|
{ "authored", "🗪", "Chat Comment"}, // TODO(matt): Conditionally handle Chat vs Guest Comments
|
|
{ "blackboard", "🖌", "Blackboard"},
|
|
{ "default", "🖮", "Programming"}, // TODO(matt): Potentially make this configurable per project
|
|
{ "experience", "🍷", "Experience"},
|
|
{ "owl", "🦉", "Owl of Shame"},
|
|
{ "rant", "💢", "Rant"},
|
|
{ "research", "📖", "Research"},
|
|
{ "run", "🏃", "In-Game"}, // TODO(matt): Potentially make this configurable per project
|
|
{ "trivia", "🎲", "Trivia"},
|
|
};
|
|
|
|
void
|
|
BuildFilter(category_info *TopicsArray, int *UniqueTopics, category_info *MediaArray, int *UniqueMedia, char *Marker)
|
|
{
|
|
bool IsMedium = FALSE;
|
|
|
|
int i = 0;
|
|
for(i = 0; i < ArrayCount(CategoryMedium); ++i)
|
|
{
|
|
if(!StringsDiffer(CategoryMedium[i][0], Marker))
|
|
{
|
|
IsMedium = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
int Offset;
|
|
if(IsMedium)
|
|
{
|
|
int j = 0;
|
|
for(j = 0; j < *UniqueMedia; ++j)
|
|
{
|
|
if(!StringsDiffer(CategoryMedium[i][0], MediaArray[j].Marker))
|
|
{
|
|
return;
|
|
}
|
|
if((Offset = StringsDiffer(CategoryMedium[i][2], MediaArray[j].WrittenText)) < 0)
|
|
{
|
|
int k;
|
|
for(k = *UniqueMedia; k > j; --k)
|
|
{
|
|
CopyString(MediaArray[k].Marker, MediaArray[k-1].Marker);
|
|
CopyString(MediaArray[k].WrittenText, MediaArray[k-1].WrittenText);
|
|
}
|
|
|
|
CopyString(MediaArray[k].Marker, CategoryMedium[i][0]);
|
|
CopyString(MediaArray[k].WrittenText, CategoryMedium[i][2]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(j == *UniqueMedia)
|
|
{
|
|
CopyString(MediaArray[j].Marker, CategoryMedium[i][0]);
|
|
CopyString(MediaArray[j].WrittenText, CategoryMedium[i][2]);
|
|
}
|
|
|
|
++*UniqueMedia;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
int i = 0;
|
|
for(i = 0; i < *UniqueTopics; ++i)
|
|
{
|
|
if(!StringsDiffer(Marker, TopicsArray[i].Marker))
|
|
{
|
|
return;
|
|
}
|
|
if((Offset = StringsDiffer(Marker, TopicsArray[i].Marker)) < 0)
|
|
{
|
|
int j;
|
|
for(j = *UniqueTopics; j > i; --j)
|
|
{
|
|
CopyString(TopicsArray[j].Marker, TopicsArray[j-1].Marker);
|
|
}
|
|
|
|
CopyString(TopicsArray[j].Marker, Marker);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(i == *UniqueTopics)
|
|
{
|
|
CopyString(TopicsArray[i].Marker, Marker);
|
|
}
|
|
|
|
++*UniqueTopics;
|
|
return;
|
|
}
|
|
}
|
|
|
|
void
|
|
BuildCategories(buffer *AnnotationClass, buffer *Category, int *MarkerIndex, bool *HasCategory, bool *HasMedium, char *Marker)
|
|
{
|
|
// NOTE(matt): This guy could also sort, so that the dots appear in a consistent order in the annotations
|
|
// If so, the Category buffer would have to only contain the category names and no more until collation time
|
|
// BuildCategories() would have to parse the Category.Location out to an array, sort that array and write it back in
|
|
// The code in the "annotation loop" would then have to write both the head and tail of the category stuff
|
|
for(int i = 0; i < ArrayCount(CategoryMedium); ++i)
|
|
{
|
|
if(!StringsDiffer(CategoryMedium[i][0], Marker))
|
|
{
|
|
CopyStringToBuffer(AnnotationClass, " %s", SanitisePunctuation(Marker));
|
|
*HasMedium = TRUE;
|
|
++*MarkerIndex;
|
|
return;
|
|
}
|
|
}
|
|
if(*HasCategory == FALSE)
|
|
{
|
|
CopyStringToBuffer(Category, "<span class=\"categories\">");
|
|
*HasCategory = TRUE;
|
|
}
|
|
|
|
CopyStringToBuffer(Category, "<div class=\"category %s\"></div>",
|
|
SanitisePunctuation(Marker));
|
|
|
|
CopyStringToBuffer(AnnotationClass, " cat_%s",
|
|
SanitisePunctuation(Marker));
|
|
++*MarkerIndex;
|
|
return;
|
|
}
|
|
|
|
int
|
|
StringToInt(char *String)
|
|
{
|
|
int Result = 0;
|
|
while(*String)
|
|
{
|
|
Result = Result * 10 + (*String - '0');
|
|
++String;
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
int
|
|
BuildQuote(quote_info *Info, char *Speaker, int ID)
|
|
{
|
|
// TODO(matt): Pull these paths from the config
|
|
// Also notable that there are different paths for different projects
|
|
//
|
|
// TODO(matt): Handle csv's double quote escaping stuff
|
|
|
|
char *QuoteDir = "/home/matt/git/GitHub/insofaras/25fc16d58a297a486334";
|
|
if(!StringsDiffer(Speaker, "handmade_hero"))
|
|
{
|
|
QuoteDir = "/home/matt/git/GitHub/Chronister/01e754a09fd84c839ad7";
|
|
}
|
|
|
|
char Path[255] = {0};
|
|
sprintf(Path, "%s/#%s", QuoteDir, Speaker);
|
|
FILE *File;
|
|
if(!(File = fopen(Path, "r")))
|
|
{
|
|
perror("hmml_to_html");
|
|
return 1;
|
|
}
|
|
|
|
fseek(File, 0, SEEK_END);
|
|
int Length = ftell(File);
|
|
fseek(File, 0, SEEK_SET);
|
|
char *Buffer;
|
|
if(!(Buffer = malloc(Length)))
|
|
{
|
|
perror("hmml_to_html");
|
|
}
|
|
fread(Buffer, Length, 1, File);
|
|
fclose(File);
|
|
|
|
// TODO(matt): Search the quote store in reverse
|
|
char *InPtr = Buffer;
|
|
|
|
while(InPtr - Buffer < Length)
|
|
{
|
|
|
|
char InID[4] = { 0 };
|
|
char *OutPtr = InID;
|
|
while(*InPtr != ',')
|
|
{
|
|
*OutPtr++ = *InPtr++;
|
|
}
|
|
*OutPtr = '\0';
|
|
|
|
if(StringToInt(InID) == ID)
|
|
{
|
|
InPtr += 2;
|
|
OutPtr = Info->Date;
|
|
while(*InPtr != '"')
|
|
{
|
|
*OutPtr++ = *InPtr++;
|
|
}
|
|
*OutPtr = '\0';
|
|
|
|
InPtr += 3;
|
|
OutPtr = Info->Text;
|
|
while(*InPtr != '\n')
|
|
{
|
|
if(*InPtr == '\\')
|
|
{
|
|
++InPtr;
|
|
}
|
|
*OutPtr++ = *InPtr++;
|
|
}
|
|
*--OutPtr = '\0';
|
|
|
|
free(Buffer);
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
while(*InPtr != '\n')
|
|
{
|
|
++InPtr;
|
|
}
|
|
++InPtr;
|
|
}
|
|
}
|
|
|
|
free(Buffer);
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
GenerateTopicColours(buffer *Colour, char *Topic)
|
|
{
|
|
for(int i = 0; i < ArrayCount(CategoryMedium); ++i)
|
|
{
|
|
if(!StringsDiffer(Topic, CategoryMedium[i][0]))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
FILE *TopicsFile;
|
|
char *TopicsBuffer;
|
|
// TODO(matt): Consider (optionally) pulling this path from the config
|
|
if((TopicsFile = fopen("topics.css", "a+")))
|
|
{
|
|
fseek(TopicsFile, 0, SEEK_END);
|
|
int TopicsLength = ftell(TopicsFile);
|
|
fseek(TopicsFile, 0, SEEK_SET);
|
|
|
|
// TODO(matt): May this not just ClaimBuffer (if I can figure out how)?
|
|
if(!(TopicsBuffer = malloc(TopicsLength)))
|
|
{
|
|
perror("GenerateTopicColours");
|
|
return;
|
|
}
|
|
|
|
fread(TopicsBuffer, TopicsLength, 1, TopicsFile);
|
|
|
|
char *TopicsPtr = TopicsBuffer;
|
|
|
|
while(TopicsPtr - TopicsBuffer < TopicsLength)
|
|
{
|
|
TopicsPtr += StringLength(".category.");
|
|
if(!StringsDifferL(SanitisePunctuation(Topic), TopicsPtr, StringLength(Topic)))
|
|
{
|
|
free(TopicsBuffer);
|
|
fclose(TopicsFile);
|
|
return;
|
|
}
|
|
while(TopicsPtr - TopicsBuffer < TopicsLength && *TopicsPtr != '\n')
|
|
{
|
|
++TopicsPtr;
|
|
}
|
|
++TopicsPtr;
|
|
}
|
|
|
|
hsl_colour Colour;
|
|
StringToColourHash(&Colour, Topic);
|
|
fprintf(TopicsFile, ".category.%s { border: 1px solid hsl(%d, %d%%, %d%%); background: hsl(%d, %d%%, %d%%); }\n",
|
|
SanitisePunctuation(Topic), Colour.Hue, Colour.Saturation, Colour.Lightness, Colour.Hue, Colour.Saturation, Colour.Lightness);
|
|
|
|
fclose(TopicsFile);
|
|
free(TopicsBuffer);
|
|
}
|
|
else
|
|
{
|
|
perror("GenerateTopicColours");
|
|
return;
|
|
}
|
|
}
|
|
|
|
#define CONFIG 0
|
|
|
|
#if CONFIG
|
|
typedef struct
|
|
{
|
|
char *Username;
|
|
char *Display_Name;
|
|
char *Homepage;
|
|
char *Funding_Platform;
|
|
char *Funding_Username;
|
|
unsigned int Index;
|
|
} credentials;
|
|
|
|
typedef struct
|
|
{
|
|
credentials Credentials;
|
|
} config;
|
|
|
|
int
|
|
ParseConfig(buffer *Buffer, char *Username)
|
|
{
|
|
/*
|
|
Essentially, I want to pass a Username to this, and have it write the credentials into the Config buffer
|
|
Let's start by just grabbing the stuff and printing it out
|
|
*/
|
|
|
|
// TODO(matt): Actually figure out the "standard" config location
|
|
char Config_Location[255];
|
|
if(getenv("XDG_CONFIG_HOME"))
|
|
{
|
|
sprintf(Config_Location, "%s/hmml.conf", getenv("XDG_CONFIG_HOME"));
|
|
}
|
|
else if(getenv("HOME"))
|
|
{
|
|
sprintf(Config_Location, "%s/.config/hmml.conf", getenv("HOME"));
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, "Config file location not set");
|
|
return 1;
|
|
}
|
|
|
|
FILE *InFile;
|
|
if(!(InFile = fopen(Config_Location, "r")))
|
|
{
|
|
perror(Config_Location);
|
|
return 2;
|
|
}
|
|
|
|
printf("Reading: %s\n", Config_Location);
|
|
|
|
fseek(InFile, 0, SEEK_END);
|
|
int InSize = ftell(InFile);
|
|
fseek(InFile, 0, SEEK_SET);
|
|
|
|
char *InBuffer;
|
|
//config Config = { 0 };
|
|
|
|
if(!(InBuffer = malloc(InSize)))
|
|
{
|
|
perror("ParseConfig");
|
|
return 3;
|
|
}
|
|
|
|
fread(InBuffer, InSize, 1, InFile);
|
|
fclose(InFile);
|
|
|
|
char *InPtr = InBuffer;
|
|
char OutBuffer[256];
|
|
//char *OutPtr = Config.Credentials.Display_Name;
|
|
char *OutPtr = OutBuffer;
|
|
bool Quoted = FALSE;
|
|
bool FoundCredentials, ParsingUsername = FALSE;
|
|
unsigned int ScopeDepth = 0;
|
|
|
|
while(InPtr - InBuffer < InSize)
|
|
{
|
|
switch(*InPtr)
|
|
{
|
|
case '#':
|
|
{
|
|
if(!Quoted)
|
|
{
|
|
printf(" We are commenting\n");
|
|
while(InPtr - InBuffer < InSize && *InPtr != '\n')
|
|
{
|
|
++InPtr;
|
|
}
|
|
++InPtr;
|
|
while(InPtr - InBuffer < InSize && (*InPtr == ' ' || *InPtr == '\n'))
|
|
{
|
|
++InPtr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*OutPtr++ = *InPtr++;
|
|
}
|
|
break;
|
|
}
|
|
case '{':
|
|
{
|
|
if(!Quoted)
|
|
{
|
|
++ScopeDepth;
|
|
++InPtr;
|
|
while(*InPtr == '\n' || *InPtr == ' ')
|
|
{
|
|
++InPtr;
|
|
}
|
|
printf(" We have entered a scope\n");
|
|
}
|
|
else
|
|
{
|
|
*OutPtr++ = *InPtr++;
|
|
}
|
|
break;
|
|
}
|
|
case '}':
|
|
{
|
|
if(!Quoted)
|
|
{
|
|
--ScopeDepth;
|
|
++InPtr;
|
|
printf(" We have left a scope\n");
|
|
}
|
|
else
|
|
{
|
|
*OutPtr++ = *InPtr++;
|
|
}
|
|
break;
|
|
}
|
|
#if 1
|
|
case ' ':
|
|
{
|
|
if(!Quoted)
|
|
{
|
|
++InPtr;
|
|
*OutPtr = '\0';
|
|
OutPtr = OutBuffer;
|
|
printf("%s\n", OutBuffer);
|
|
|
|
// TODO(matt): Switch on the OutBuffer? I have a feeling that isn't actually possible, though
|
|
if(!StringsDiffer("credentials", OutBuffer))
|
|
{
|
|
FoundCredentials = TRUE;
|
|
printf(" We have found the credentials block\n");
|
|
}
|
|
if(ParsingUsername)
|
|
{
|
|
printf(" The username is %s\n", OutBuffer);
|
|
ParsingUsername = FALSE;
|
|
}
|
|
if(FoundCredentials && (!StringsDiffer("username", OutBuffer)))
|
|
{
|
|
ParsingUsername = TRUE;
|
|
printf(" We have found the username\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*OutPtr++ = *InPtr++;
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
case '"':
|
|
{
|
|
if(!Quoted)
|
|
{
|
|
Quoted = TRUE;
|
|
printf(" We are quoting!\n");
|
|
}
|
|
else
|
|
{
|
|
Quoted = FALSE;
|
|
printf(" We are no longer quoting!\n");
|
|
}
|
|
++InPtr;
|
|
break;
|
|
}
|
|
case ';':
|
|
{
|
|
if(!Quoted)
|
|
{
|
|
printf(" We have reached the end of a setting\n");
|
|
++InPtr;
|
|
}
|
|
else
|
|
{
|
|
*OutPtr++ = *InPtr++;
|
|
}
|
|
}
|
|
case '\n':
|
|
{
|
|
if(!Quoted)
|
|
{
|
|
if(InPtr - InBuffer < InSize)
|
|
{
|
|
*OutPtr = '\0';
|
|
OutPtr = OutBuffer;
|
|
// TODO(matt)
|
|
if(!StringsDiffer("credentials", OutBuffer))
|
|
{
|
|
FoundCredentials = TRUE;
|
|
printf(" We have found the credentials block\n");
|
|
}
|
|
if(ParsingUsername)
|
|
{
|
|
printf(" The username is %s\n", OutBuffer);
|
|
ParsingUsername = FALSE;
|
|
}
|
|
if(FoundCredentials && (!StringsDiffer("username", OutBuffer)))
|
|
{
|
|
ParsingUsername = TRUE;
|
|
printf(" We have found the username\n");
|
|
}
|
|
printf("%s\n", OutBuffer);
|
|
++InPtr;
|
|
while(InPtr - InBuffer < InSize && *InPtr == ' ') // NOTE(matt): Skip indentation whitespace
|
|
{
|
|
++InPtr;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*OutPtr++ = *InPtr++;
|
|
}
|
|
|
|
if(InPtr - InBuffer == InSize)
|
|
{
|
|
printf(" We have reached the EOF\n");
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
*OutPtr++ = *InPtr++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#if 0
|
|
while(InPtr - InBuffer < InSize)
|
|
{
|
|
while(*InPtr != '\n')
|
|
{
|
|
*OutPtr++ = *InPtr++;
|
|
}
|
|
*OutPtr = '\0';
|
|
printf("%s\n", Config.Credentials.Display_Name);
|
|
}
|
|
#endif
|
|
|
|
free(InBuffer);
|
|
|
|
// Reading from the config file, parsing it inline (on the stack) and writing into the buffer *Config
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
int
|
|
main(int ArgC, char **Args)
|
|
{
|
|
if(ArgC < 2)
|
|
{
|
|
fprintf(stderr, "Usage: %s filename(s)\n", Args[0]);
|
|
return 1;
|
|
}
|
|
|
|
// NOTE(matt): Init MemoryArena
|
|
char *MemoryArena;
|
|
int ArenaSize = Megabytes(4);
|
|
if(!(MemoryArena = calloc(ArenaSize, 1)))
|
|
{
|
|
perror(Args[0]);
|
|
return 1;
|
|
}
|
|
int ClaimedMemory = 0;
|
|
|
|
// NOTE(matt): Setup buffers and ptrs
|
|
char *InPtr;
|
|
|
|
#if CONFIG
|
|
buffer Config;
|
|
#endif
|
|
|
|
// NOTE(matt): Tree structure of buffer dependencies
|
|
// Master
|
|
// Title
|
|
// QuoteMenu
|
|
// ReferenceMenu
|
|
// FilterMenu
|
|
// FilterTopics
|
|
// FilterMedia
|
|
// Player
|
|
// Colour
|
|
// Annotation
|
|
// AnnotationHeader
|
|
// AnnotationClass
|
|
// AnnotationData
|
|
// Text
|
|
// Category
|
|
// Script
|
|
// FilterState
|
|
|
|
buffer Master;
|
|
|
|
buffer Title;
|
|
buffer QuoteMenu;
|
|
buffer ReferenceMenu;
|
|
buffer FilterMenu;
|
|
buffer FilterTopics;
|
|
buffer FilterMedia;
|
|
buffer CreditsMenu;
|
|
buffer HostInfo;
|
|
buffer AnnotatorInfo;
|
|
|
|
buffer Player;
|
|
buffer Colour;
|
|
buffer Annotation;
|
|
buffer AnnotationHeader;
|
|
buffer AnnotationClass;
|
|
buffer AnnotationData;
|
|
buffer Text;
|
|
buffer Category;
|
|
|
|
buffer Script;
|
|
buffer FilterState;
|
|
|
|
for(int FileIndex = 1; FileIndex < ArgC; ++FileIndex)
|
|
{
|
|
FILE *InFile;
|
|
if(!(InFile = fopen(Args[FileIndex], "r")))
|
|
{
|
|
perror(Args[0]);
|
|
free(MemoryArena);
|
|
return 1;
|
|
}
|
|
|
|
#if CONFIG
|
|
ClaimBuffer(MemoryArena, &ClaimedMemory, &Config, "Config", Kilobytes(1));
|
|
#endif
|
|
|
|
HMML_Output HMML = hmml_parse_file(InFile);
|
|
fclose(InFile);
|
|
|
|
if(HMML.well_formed)
|
|
{
|
|
// NOTE(matt): Tree structure of "global" buffer dependencies
|
|
// Master
|
|
// Title
|
|
// QuoteMenu
|
|
// ReferenceMenu
|
|
// FilterMenu
|
|
// FilterTopics
|
|
// FilterMedia
|
|
// CreditsMenu
|
|
// Player
|
|
// Colour
|
|
// Annotation
|
|
// Script
|
|
// FilterState
|
|
|
|
ClaimBuffer(MemoryArena, &ClaimedMemory, &Master, "Master", Kilobytes(512));
|
|
|
|
ClaimBuffer(MemoryArena, &ClaimedMemory, &Title, "Title", Kilobytes(16));
|
|
ClaimBuffer(MemoryArena, &ClaimedMemory, &QuoteMenu, "QuoteMenu", Kilobytes(16));
|
|
ClaimBuffer(MemoryArena, &ClaimedMemory, &ReferenceMenu, "ReferenceMenu", Kilobytes(16));
|
|
ClaimBuffer(MemoryArena, &ClaimedMemory, &FilterMenu, "FilterMenu", Kilobytes(16));
|
|
ClaimBuffer(MemoryArena, &ClaimedMemory, &FilterTopics, "FilterTopics", Kilobytes(8));
|
|
ClaimBuffer(MemoryArena, &ClaimedMemory, &FilterMedia, "FilterMedia", Kilobytes(8));
|
|
ClaimBuffer(MemoryArena, &ClaimedMemory, &CreditsMenu, "CreditsMenu", Kilobytes(8));
|
|
ClaimBuffer(MemoryArena, &ClaimedMemory, &HostInfo, "HostInfo", Kilobytes(1));
|
|
ClaimBuffer(MemoryArena, &ClaimedMemory, &AnnotatorInfo, "AnnotatorInfo", Kilobytes(1));
|
|
|
|
ClaimBuffer(MemoryArena, &ClaimedMemory, &Player, "Player", Kilobytes(256));
|
|
ClaimBuffer(MemoryArena, &ClaimedMemory, &Colour, "Colour", 32);
|
|
ClaimBuffer(MemoryArena, &ClaimedMemory, &Annotation, "Annotation", Kilobytes(8));
|
|
|
|
ClaimBuffer(MemoryArena, &ClaimedMemory, &Script, "Script", Kilobytes(8));
|
|
ClaimBuffer(MemoryArena, &ClaimedMemory, &FilterState, "FilterState", Kilobytes(4));
|
|
|
|
ref_info ReferencesArray[200] = { 0 };
|
|
category_info TopicsArray[56] = { 0 };
|
|
category_info MediaArray[8] = { 0 };
|
|
|
|
bool HasQuoteMenu = FALSE;
|
|
bool HasReferenceMenu = FALSE;
|
|
bool HasFilterMenu = FALSE;
|
|
bool HasCreditsMenu = FALSE;
|
|
|
|
int QuoteIdentifier = 0x3b1;
|
|
int RefIdentifier = 1;
|
|
int UniqueRefs = 0;
|
|
int UniqueTopics = 0;
|
|
int UniqueMedia = 0;
|
|
|
|
CopyStringToBuffer(&Title,
|
|
" <div class=\"title %s\">\n"
|
|
" <span class=\"episode_name\">", HMML.metadata.project);
|
|
CopyStringToBufferHTMLSafe(&Title, HMML.metadata.title);
|
|
CopyStringToBuffer(&Title, "</span>\n"
|
|
" <span id=\"focus-warn\">⚠ Click here to regain focus ⚠</span>\n");
|
|
|
|
CopyStringToBuffer(&Player,
|
|
" <div class=\"player_container\">\n"
|
|
" <div class=\"video_container\" data-videoId=\"%s\"></div>\n"
|
|
" <div class=\"markers_container %s\">\n", HMML.metadata.id, HMML.metadata.project);
|
|
|
|
BuildCredits(&CreditsMenu, &HostInfo, &AnnotatorInfo, &HasCreditsMenu, HMML.metadata.member, HMML.metadata.annotator);
|
|
|
|
#if DEBUG
|
|
printf(" --- Entering Annotations Loop ---\n");
|
|
#endif
|
|
|
|
for(int AnnotationIndex = 0; AnnotationIndex < HMML.annotation_count; ++AnnotationIndex)
|
|
{
|
|
#if DEBUG
|
|
printf("%d\n", AnnotationIndex);
|
|
#endif
|
|
HMML_Annotation *Anno = HMML.annotations + AnnotationIndex;
|
|
bool HasCategory = FALSE;
|
|
bool HasMedium = FALSE;
|
|
bool HasQuote = FALSE;
|
|
bool HasReference = FALSE;
|
|
|
|
quote_info QuoteInfo = { 0 };
|
|
|
|
// NOTE(matt): Tree structure of "annotation local" buffer dependencies
|
|
// AnnotationHeader
|
|
// AnnotationClass
|
|
// AnnotationData
|
|
// Text
|
|
// Category
|
|
|
|
ClaimBuffer(MemoryArena, &ClaimedMemory, &AnnotationHeader, "AnnotationHeader", 512);
|
|
ClaimBuffer(MemoryArena, &ClaimedMemory, &AnnotationClass, "AnnotationClass", 256);
|
|
ClaimBuffer(MemoryArena, &ClaimedMemory, &AnnotationData, "AnnotationData", 256);
|
|
ClaimBuffer(MemoryArena, &ClaimedMemory, &Text, "Text", Kilobytes(4));
|
|
ClaimBuffer(MemoryArena, &ClaimedMemory, &Category, "Category", 256);
|
|
|
|
CopyStringToBuffer(&AnnotationHeader,
|
|
" <div data-timestamp=\"%d\"",
|
|
TimecodeToSeconds(Anno->time));
|
|
|
|
CopyStringToBuffer(&AnnotationClass,
|
|
" class=\"marker");
|
|
|
|
if(Anno->author)
|
|
{
|
|
if(!HasFilterMenu)
|
|
{
|
|
HasFilterMenu = TRUE;
|
|
}
|
|
BuildFilter(TopicsArray, &UniqueTopics, MediaArray, &UniqueMedia, "authored");
|
|
CopyStringToBuffer(&AnnotationClass, " authored");
|
|
hsl_colour AuthorColour;
|
|
StringToColourHash(&AuthorColour, Anno->author);
|
|
CopyStringToBuffer(&Text,
|
|
"<span class=\"author\" style=\"color: hsl(%d, %d%%, %d%%);\" data-hue=\"%d\" data-saturation=\"%d%%\">%s</span> ",
|
|
AuthorColour.Hue, AuthorColour.Saturation, AuthorColour.Lightness,
|
|
AuthorColour.Hue, AuthorColour.Saturation,
|
|
Anno->author);
|
|
if(!HasMedium)
|
|
{
|
|
HasMedium = TRUE;
|
|
}
|
|
}
|
|
|
|
InPtr = Anno->text;
|
|
|
|
int MarkerIndex = 0, RefIndex = 0;
|
|
while(*InPtr || RefIndex < Anno->reference_count)
|
|
{
|
|
if(MarkerIndex < Anno->marker_count &&
|
|
InPtr - Anno->text == Anno->markers[MarkerIndex].offset)
|
|
{
|
|
char *Readable = Anno->markers[MarkerIndex].parameter
|
|
? Anno->markers[MarkerIndex].parameter
|
|
: Anno->markers[MarkerIndex].marker;
|
|
if(Anno->markers[MarkerIndex].type == HMML_MEMBER)
|
|
{
|
|
hsl_colour MemberColour;
|
|
StringToColourHash(&MemberColour, Anno->markers[MarkerIndex].marker);
|
|
CopyStringToBuffer(&Text,
|
|
// TODO(matt): Hoverbox
|
|
// We should get instructions on how to get this info in the config
|
|
"<a class=\"member\" href=\"https://handmade.network/m/%s\" target=\"blank\" style=\"color: hsl(%d, %d%%, %d%%); text-decoration: none\" data-hue=\"%d\" data-saturation=\"%d%%\">%.*s</a>",
|
|
Anno->markers[MarkerIndex].marker,
|
|
MemberColour.Hue, MemberColour.Saturation, MemberColour.Lightness,
|
|
MemberColour.Hue, MemberColour.Saturation,
|
|
StringLength(Readable), InPtr);
|
|
InPtr += StringLength(Readable);
|
|
++MarkerIndex;
|
|
}
|
|
else if(Anno->markers[MarkerIndex].type == HMML_PROJECT)
|
|
{
|
|
hsl_colour ProjectColour;
|
|
StringToColourHash(&ProjectColour, Anno->markers[MarkerIndex].marker);
|
|
CopyStringToBuffer(&Text,
|
|
// TODO(matt): Hoverbox
|
|
// We should get instructions on how to get this info in the config
|
|
"<a class=\"project\" href=\"https://%s.handmade.network/\" target=\"blank\" style=\"color: hsl(%d, %d%%, %d%%); text-decoration: none\" data-hue=\"%d\" data-saturation=\"%d%%\">%s</a>",
|
|
Anno->markers[MarkerIndex].marker,
|
|
ProjectColour.Hue, ProjectColour.Saturation, ProjectColour.Lightness,
|
|
ProjectColour.Hue, ProjectColour.Saturation,
|
|
Readable);
|
|
InPtr += StringLength(Readable);
|
|
++MarkerIndex;
|
|
}
|
|
else if(Anno->markers[MarkerIndex].type == HMML_CATEGORY)
|
|
{
|
|
GenerateTopicColours(&Colour, Anno->markers[MarkerIndex].marker);
|
|
// TODO(matt): Maybe stuff this into BuildCategories
|
|
if(!HasFilterMenu)
|
|
{
|
|
HasFilterMenu = TRUE;
|
|
}
|
|
BuildFilter(TopicsArray, &UniqueTopics, MediaArray, &UniqueMedia, Anno->markers[MarkerIndex].marker);
|
|
BuildCategories(&AnnotationClass, &Category, &MarkerIndex, &HasCategory, &HasMedium, Anno->markers[MarkerIndex].marker);
|
|
}
|
|
}
|
|
|
|
if(RefIndex < Anno->reference_count &&
|
|
InPtr - Anno->text == Anno->references[RefIndex].offset)
|
|
{
|
|
HMML_Reference *CurrentRef = Anno->references + RefIndex;
|
|
if(!HasReferenceMenu)
|
|
{
|
|
CopyStringToBuffer(&ReferenceMenu,
|
|
" <div class=\"menu references\">\n"
|
|
" <span>References ▼</span>\n"
|
|
" <div class=\"mouse_catcher\"></div>\n"
|
|
" <div class=\"refs references_container\">\n");
|
|
|
|
if(BuildReference(ReferencesArray, RefIdentifier, UniqueRefs, *CurrentRef, *Anno) == 1)
|
|
{
|
|
fprintf(stderr, "%s:%d: Cannot process new combination of reference info\n", Args[FileIndex], Anno->line);
|
|
hmml_free(&HMML);
|
|
free(MemoryArena);
|
|
return 1;
|
|
}
|
|
++ReferencesArray[RefIdentifier - 1].IdentifierCount;
|
|
++UniqueRefs;
|
|
|
|
HasReferenceMenu = TRUE;
|
|
}
|
|
else
|
|
{
|
|
for(int i = 0; i < UniqueRefs; ++i)
|
|
{
|
|
if(CurrentRef->isbn)
|
|
{
|
|
if(!StringsDiffer(CurrentRef->isbn, ReferencesArray[i].ID))
|
|
{
|
|
CopyString(ReferencesArray[i].Identifier[ReferencesArray[i].IdentifierCount].Timecode, Anno->time);
|
|
ReferencesArray[i].Identifier[ReferencesArray[i].IdentifierCount].Identifier = RefIdentifier;
|
|
++ReferencesArray[i].IdentifierCount;
|
|
goto AppendedIdentifier;
|
|
}
|
|
}
|
|
else if(CurrentRef->url)
|
|
{
|
|
if(!StringsDiffer(CurrentRef->url, ReferencesArray[i].ID))
|
|
{
|
|
CopyString(ReferencesArray[i].Identifier[ReferencesArray[i].IdentifierCount].Timecode, Anno->time);
|
|
ReferencesArray[i].Identifier[ReferencesArray[i].IdentifierCount].Identifier = RefIdentifier;
|
|
++ReferencesArray[i].IdentifierCount;
|
|
goto AppendedIdentifier;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, "%s:%d: Reference must have an ISBN or URL\n", Args[FileIndex], Anno->line);
|
|
hmml_free(&HMML);
|
|
free(MemoryArena);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if(BuildReference(ReferencesArray, RefIdentifier, UniqueRefs, *CurrentRef, *Anno) == 1)
|
|
{
|
|
fprintf(stderr, "%s:%d: Cannot process new combination of reference info\n", Args[FileIndex], Anno->line);
|
|
hmml_free(&HMML);
|
|
free(MemoryArena);
|
|
return 1;
|
|
}
|
|
++ReferencesArray[UniqueRefs].IdentifierCount;
|
|
++UniqueRefs;
|
|
}
|
|
AppendedIdentifier:
|
|
if(!HasReference)
|
|
{
|
|
if(CurrentRef->isbn)
|
|
{
|
|
CopyStringToBuffer(&AnnotationData, " data-ref=\"%s", CurrentRef->isbn);
|
|
}
|
|
else if(CurrentRef->url)
|
|
{
|
|
CopyStringToBuffer(&AnnotationData, " data-ref=\"%s", CurrentRef->url);
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, "%s:%d: Reference must have an ISBN or URL\n", Args[FileIndex], Anno->line);
|
|
hmml_free(&HMML);
|
|
free(MemoryArena);
|
|
return 1;
|
|
}
|
|
|
|
HasReference = TRUE;
|
|
}
|
|
else
|
|
{
|
|
if(CurrentRef->isbn)
|
|
{
|
|
CopyStringToBuffer(&AnnotationData, ",%s", CurrentRef->isbn);
|
|
}
|
|
else if(CurrentRef->url)
|
|
{
|
|
CopyStringToBuffer(&AnnotationData, ",%s", CurrentRef->url);
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, "%s:%d: Reference must have an ISBN or URL", Args[FileIndex], Anno->line);
|
|
hmml_free(&HMML);
|
|
free(MemoryArena);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if(RefIndex > 1 && Anno->references[RefIndex].offset == Anno->references[RefIndex-1].offset)
|
|
{
|
|
CopyStringToBuffer(&Text, "<sup>,%d</sup>", RefIdentifier);
|
|
}
|
|
else
|
|
{
|
|
CopyStringToBuffer(&Text, "<sup>%d</sup>", RefIdentifier);
|
|
}
|
|
|
|
++RefIndex;
|
|
++RefIdentifier;
|
|
}
|
|
|
|
if(*InPtr)
|
|
{
|
|
switch(*InPtr)
|
|
{
|
|
case '<':
|
|
CopyStringToBuffer(&Text, "<");
|
|
InPtr++;
|
|
break;
|
|
case '>':
|
|
CopyStringToBuffer(&Text, ">");
|
|
InPtr++;
|
|
break;
|
|
case '&':
|
|
CopyStringToBuffer(&Text, "&");
|
|
InPtr++;
|
|
break;
|
|
case '\"':
|
|
CopyStringToBuffer(&Text, """);
|
|
InPtr++;
|
|
break;
|
|
case '\'':
|
|
CopyStringToBuffer(&Text, "'");
|
|
InPtr++;
|
|
break;
|
|
default:
|
|
*Text.Ptr++ = *InPtr++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(Anno->is_quote)
|
|
{
|
|
if(!HasQuoteMenu)
|
|
{
|
|
CopyStringToBuffer(&QuoteMenu,
|
|
" <div class=\"menu quotes\">\n"
|
|
" <span>Quotes ▼</span>\n"
|
|
" <div class=\"mouse_catcher\"></div>\n"
|
|
" <div class=\"refs quotes_container\">\n");
|
|
|
|
HasQuoteMenu = TRUE;
|
|
}
|
|
|
|
if(!HasReference)
|
|
{
|
|
CopyStringToBuffer(&AnnotationData, " data-ref=\"&#%d;", QuoteIdentifier);
|
|
}
|
|
else
|
|
{
|
|
CopyStringToBuffer(&AnnotationData, ",&#%d;", QuoteIdentifier);
|
|
}
|
|
|
|
HasQuote = TRUE;
|
|
|
|
|
|
if(BuildQuote(&QuoteInfo, HMML.metadata.twitch ? HMML.metadata.twitch : HMML.metadata.member, Anno->quote.id) == 1)
|
|
{
|
|
fprintf(stderr, "%s:%d: Quote #%s %d not found! Consider pulling the latest quotes\n",
|
|
Args[FileIndex],
|
|
Anno->line,
|
|
HMML.metadata.twitch ? HMML.metadata.twitch : HMML.metadata.member,
|
|
Anno->quote.id);
|
|
hmml_free(&HMML);
|
|
free(MemoryArena);
|
|
return 1;
|
|
}
|
|
|
|
CopyStringToBuffer(&QuoteMenu,
|
|
" <span data-id=\"&#%d;\" class=\"ref\">\n"
|
|
" <span class=\"ref_content\">\n"
|
|
" <div class=\"source\">Quote %d</div>\n"
|
|
" <div class=\"ref_title\">%s</div>\n"
|
|
" <div class=\"quote_byline\">—%s, %s</div>\n"
|
|
" </span>\n"
|
|
" <div class=\"ref_indices\">\n"
|
|
" <span data-timestamp=\"%d\" class=\"timecode\"><span class=\"ref_index\">[&#%d;]</span><span class=\"time\">%s</span></span>\n"
|
|
" </div>\n"
|
|
" </span>\n",
|
|
QuoteIdentifier,
|
|
Anno->quote.id,
|
|
QuoteInfo.Text,
|
|
HMML.metadata.twitch ? HMML.metadata.twitch : HMML.metadata.member,
|
|
QuoteInfo.Date,
|
|
TimecodeToSeconds(Anno->time),
|
|
QuoteIdentifier,
|
|
Anno->time);
|
|
if(!Anno->text[0])
|
|
{
|
|
CopyStringToBuffer(&Text, "“");
|
|
CopyStringToBufferHTMLSafe(&Text, QuoteInfo.Text);
|
|
CopyStringToBuffer(&Text, "”");
|
|
}
|
|
CopyStringToBuffer(&Text, "<sup>&#%d;</sup>", QuoteIdentifier);
|
|
++QuoteIdentifier;
|
|
}
|
|
|
|
while(MarkerIndex < Anno->marker_count)
|
|
{
|
|
GenerateTopicColours(&Colour, Anno->markers[MarkerIndex].marker);
|
|
// TODO(matt): Maybe stuff this into BuildCategories
|
|
if(!HasFilterMenu)
|
|
{
|
|
HasFilterMenu = TRUE;
|
|
}
|
|
if(Anno->markers[MarkerIndex].marker)
|
|
{
|
|
BuildFilter(TopicsArray, &UniqueTopics, MediaArray, &UniqueMedia, Anno->markers[MarkerIndex].marker);
|
|
}
|
|
BuildCategories(&AnnotationClass, &Category, &MarkerIndex, &HasCategory, &HasMedium, Anno->markers[MarkerIndex].marker);
|
|
}
|
|
|
|
if(!HasMedium)
|
|
{
|
|
BuildFilter(TopicsArray, &UniqueTopics, MediaArray, &UniqueMedia, "default");
|
|
BuildCategories(&AnnotationClass, &Category, &MarkerIndex, &HasCategory, &HasMedium, "default");
|
|
}
|
|
|
|
CopyStringToBuffer(&AnnotationClass, "\"");
|
|
CopyBuffer(&AnnotationHeader, &AnnotationClass);
|
|
|
|
if(HasQuote || HasReference)
|
|
{
|
|
CopyStringToBuffer(&AnnotationData, "\"");
|
|
CopyBuffer(&AnnotationHeader, &AnnotationData);
|
|
}
|
|
CopyStringToBuffer(&AnnotationHeader, ">\n");
|
|
|
|
CopyBuffer(&Annotation, &AnnotationHeader);
|
|
CopyStringToBuffer(&Annotation,
|
|
" <div class=\"content\"><span class=\"timecode\">%s</span>",
|
|
Anno->time);
|
|
|
|
|
|
if(HasCategory)
|
|
{
|
|
CopyStringToBuffer(&Category, "</span>");
|
|
CopyBuffer(&Text, &Category);
|
|
}
|
|
// NOTE(matt): This feels a bit janky...
|
|
*Text.Ptr = '\0';
|
|
|
|
CopyBuffer(&Annotation, &Text);
|
|
|
|
CopyStringToBuffer(&Annotation, "</div>\n"
|
|
" <div class=\"progress faded\">\n"
|
|
" <div class=\"content\"><span class=\"timecode\">%s</span>",
|
|
Anno->time);
|
|
|
|
CopyBuffer(&Annotation, &Text);
|
|
|
|
CopyStringToBuffer(&Annotation, "</div>\n"
|
|
" </div>\n"
|
|
" <div class=\"progress main\">\n"
|
|
" <div class=\"content\"><span class=\"timecode\">%s</span>",
|
|
Anno->time);
|
|
|
|
CopyBuffer(&Annotation, &Text);
|
|
|
|
CopyStringToBuffer(&Annotation, "</div>\n"
|
|
" </div>\n"
|
|
" </div>\n");
|
|
|
|
CopyBuffer(&Player, &Annotation);
|
|
|
|
// NOTE(matt): Tree structure of "annotation local" buffer dependencies
|
|
// Category
|
|
// Text
|
|
// AnnotationData
|
|
// AnnotationClass
|
|
// AnnotationHeader
|
|
|
|
DeclaimBuffer(&Category, &ClaimedMemory);
|
|
DeclaimBuffer(&Text, &ClaimedMemory);
|
|
DeclaimBuffer(&AnnotationData, &ClaimedMemory);
|
|
DeclaimBuffer(&AnnotationClass, &ClaimedMemory);
|
|
DeclaimBuffer(&AnnotationHeader, &ClaimedMemory);
|
|
Annotation.Ptr = Annotation.Location;
|
|
}
|
|
|
|
#if DEBUG
|
|
printf(" --- End of Annotations Loop ---\n\n");
|
|
#endif
|
|
if(HasQuoteMenu)
|
|
{
|
|
CopyStringToBuffer(&QuoteMenu,
|
|
" </div>\n"
|
|
" </div>\n");
|
|
CopyBuffer(&Title, &QuoteMenu);
|
|
}
|
|
|
|
if(HasReferenceMenu)
|
|
{
|
|
for(int i = 0; i < UniqueRefs; ++i)
|
|
{
|
|
CopyStringToBuffer(&ReferenceMenu,
|
|
" <a data-id=\"%s\" href=\"%s\" target=\"_blank\" class=\"ref\">\n"
|
|
" <span class=\"ref_content\">\n",
|
|
ReferencesArray[i].ID,
|
|
ReferencesArray[i].URL);
|
|
|
|
if(*ReferencesArray[i].Source)
|
|
{
|
|
CopyStringToBuffer(&ReferenceMenu,
|
|
" <div class=\"source\">");
|
|
CopyStringToBufferHTMLSafe(&ReferenceMenu, ReferencesArray[i].Source);
|
|
CopyStringToBuffer(&ReferenceMenu, "</div>\n"
|
|
" <div class=\"ref_title\">");
|
|
CopyStringToBufferHTMLSafe(&ReferenceMenu, ReferencesArray[i].RefTitle);
|
|
CopyStringToBuffer(&ReferenceMenu, "</div>\n");
|
|
}
|
|
else
|
|
{
|
|
CopyStringToBuffer(&ReferenceMenu,
|
|
" <div class=\"ref_title\">");
|
|
CopyStringToBufferHTMLSafe(&ReferenceMenu, ReferencesArray[i].RefTitle);
|
|
CopyStringToBuffer(&ReferenceMenu, "</div>\n");
|
|
}
|
|
CopyStringToBuffer(&ReferenceMenu,
|
|
" </span>\n");
|
|
|
|
for(int j = 0; j < ReferencesArray[i].IdentifierCount;)
|
|
{
|
|
CopyStringToBuffer(&ReferenceMenu,
|
|
" <div class=\"ref_indices\">\n ");
|
|
for(int k = 0; k < 3 && j < ReferencesArray[i].IdentifierCount; ++k, ++j)
|
|
{
|
|
CopyStringToBuffer(&ReferenceMenu,
|
|
"<span data-timestamp=\"%d\" class=\"timecode\"><span class=\"ref_index\">[%d]</span><span class=\"time\">%s</span></span>",
|
|
TimecodeToSeconds(ReferencesArray[i].Identifier[j].Timecode),
|
|
ReferencesArray[i].Identifier[j].Identifier,
|
|
ReferencesArray[i].Identifier[j].Timecode);
|
|
}
|
|
CopyStringToBuffer(&ReferenceMenu, "\n"
|
|
" </div>\n");
|
|
}
|
|
|
|
CopyStringToBuffer(&ReferenceMenu,
|
|
" </a>\n");
|
|
}
|
|
|
|
CopyStringToBuffer(&ReferenceMenu,
|
|
" </div>\n"
|
|
" </div>\n");
|
|
CopyBuffer(&Title, &ReferenceMenu);
|
|
}
|
|
|
|
if(HasFilterMenu)
|
|
{
|
|
CopyStringToBuffer(&FilterState, "var filterState = {\n");
|
|
for(int i = 0; i < UniqueTopics; ++i)
|
|
{
|
|
CopyStringToBuffer(&FilterState, "\"%s\":\t{ \"type\": \"%s\",\t\"off\": false },\n",
|
|
TopicsArray[i].Marker, "topic");
|
|
}
|
|
for(int i = 0; i < UniqueMedia; ++i)
|
|
{
|
|
CopyStringToBuffer(&FilterState, "\"%s\":\t{ \"type\": \"%s\",\t\"off\": false },\n",
|
|
MediaArray[i].Marker, "medium");
|
|
}
|
|
CopyStringToBuffer(&FilterState, "};\n"
|
|
"\n");
|
|
|
|
CopyStringToBuffer(&FilterMenu,
|
|
" <div class=\"menu filter\">\n"
|
|
" <span><img src=\"hues_HCL.png\"></span>\n"
|
|
" <div class=\"filter_container\">\n"
|
|
" <div class=\"filter_mode inclusive\">Filter mode: </div>\n"
|
|
" <div class=\"filters\">\n");
|
|
|
|
{
|
|
bool HasTopic = FALSE;
|
|
bool HasMedium = FALSE;
|
|
|
|
for(int i = 0; i < UniqueTopics; ++i)
|
|
{
|
|
if(!HasTopic)
|
|
{
|
|
CopyStringToBuffer(&FilterMenu,
|
|
" <div class=\"filter_topics\">\n"
|
|
" <div class=\"filter_title\">Topics</div>\n");
|
|
|
|
HasTopic = TRUE;
|
|
}
|
|
CopyStringToBuffer(&FilterTopics,
|
|
" <div class=\"filter_content %s\">\n"
|
|
" <span class=\"icon category %s\"></span><span class=\"text\">%s</span>\n"
|
|
" </div>\n",
|
|
TopicsArray[i].Marker,
|
|
TopicsArray[i].Marker,
|
|
TopicsArray[i].Marker);
|
|
}
|
|
|
|
for(int i = 0; i < UniqueMedia; ++i)
|
|
{
|
|
if(!HasMedium)
|
|
{
|
|
CopyStringToBuffer(&FilterMedia,
|
|
" <div class=\"filter_media\">\n"
|
|
" <div class=\"filter_title\">Media</div>\n");
|
|
|
|
HasMedium = TRUE;
|
|
}
|
|
|
|
int j;
|
|
for(j = 0; j < ArrayCount(CategoryMedium); ++j)
|
|
{
|
|
if(!StringsDiffer(MediaArray[i].Marker, CategoryMedium[j][0]))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
CopyStringToBuffer(&FilterMedia,
|
|
" <div class=\"filter_content %s\">\n"
|
|
" <span class=\"icon\">%s</span><span class=\"text\">%s</span>\n"
|
|
" </div>\n",
|
|
MediaArray[i].Marker,
|
|
CategoryMedium[j][1],
|
|
CategoryMedium[j][2]
|
|
);
|
|
}
|
|
|
|
if(HasTopic)
|
|
{
|
|
CopyStringToBuffer(&FilterTopics,
|
|
" </div>\n");
|
|
CopyBuffer(&FilterMenu, &FilterTopics);
|
|
}
|
|
if(HasMedium)
|
|
{
|
|
CopyStringToBuffer(&FilterMedia,
|
|
" </div>\n");
|
|
CopyBuffer(&FilterMenu, &FilterMedia);
|
|
}
|
|
}
|
|
|
|
|
|
CopyStringToBuffer(&FilterMenu,
|
|
" </div>\n"
|
|
" </div>\n"
|
|
" </div>\n");
|
|
}
|
|
|
|
CopyBuffer(&Title, &FilterMenu);
|
|
|
|
if(HasCreditsMenu)
|
|
{
|
|
CopyBuffer(&Title, &CreditsMenu);
|
|
}
|
|
|
|
#if CONFIG
|
|
// TODO(matt): Here is where I test ParseConfig
|
|
ParseConfig(&Config, HMML.metadata.annotator);
|
|
#endif
|
|
CopyStringToBuffer(&Title,
|
|
" <div class=\"help\">\n"
|
|
" <span>?</span>\n"
|
|
" <div class=\"help_container\">\n"
|
|
" <span class=\"help_key\">?</span><h1>Keyboard Navigation</h1>\n"
|
|
"\n"
|
|
" <h2>Global Keys</h2>\n"
|
|
" <span class=\"help_key\">W</span>, <span class=\"help_key\">A</span>, <span class=\"help_key\">P</span> / <span class=\"help_key\">S</span>, <span class=\"help_key\">D</span>, <span class=\"help_key\">N</span> <span class=\"help_text\">Jump to previous / next marker</span><br>\n");
|
|
|
|
if(HasFilterMenu)
|
|
{
|
|
CopyStringToBuffer(&Title,
|
|
" <span class=\"help_key\">z</span> <span class=\"help_text\">Toggle filter mode</span> <span class=\"help_key\">V</span> <span class=\"help_text\">Revert filter to original state</span>\n");
|
|
}
|
|
else
|
|
{
|
|
CopyStringToBuffer(&Title,
|
|
" <span class=\"help_key unavailable\">z</span> <span class=\"help_text unavailable\">Toggle filter mode</span> <span class=\"help_key unavailable\">V</span> <span class=\"help_text unavailable\">Revert filter to original state</span>\n");
|
|
}
|
|
|
|
CopyStringToBuffer(&Title,
|
|
"\n"
|
|
" <h2>Menu toggling</h2>\n");
|
|
|
|
if(HasQuoteMenu)
|
|
{
|
|
CopyStringToBuffer(&Title,
|
|
" <span class=\"help_key\">q</span> <span class=\"help_text\">Quotes</span>\n");
|
|
}
|
|
else
|
|
{
|
|
CopyStringToBuffer(&Title,
|
|
" <span class=\"help_key unavailable\">q</span> <span class=\"help_text unavailable\">Quotes</span>\n");
|
|
}
|
|
|
|
if(HasReferenceMenu)
|
|
{
|
|
CopyStringToBuffer(&Title,
|
|
" <span class=\"help_key\">r</span> <span class=\"help_text\">References</span>\n");
|
|
}
|
|
else
|
|
{
|
|
CopyStringToBuffer(&Title,
|
|
" <span class=\"help_key unavailable\">r</span> <span class=\"help_text unavailable\">References</span>\n");
|
|
}
|
|
|
|
if(HasFilterMenu)
|
|
{
|
|
CopyStringToBuffer(&Title,
|
|
" <span class=\"help_key\">f</span> <span class=\"help_text\">Filter</span>\n");
|
|
}
|
|
else
|
|
{
|
|
CopyStringToBuffer(&Title,
|
|
" <span class=\"help_key unavailable\">f</span> <span class=\"help_text unavailable\">Filter</span>\n");
|
|
}
|
|
|
|
if(HasCreditsMenu)
|
|
{
|
|
CopyStringToBuffer(&Title,
|
|
" <span class=\"help_key\">c</span> <span class=\"help_text\">Credits</span>\n");
|
|
}
|
|
else
|
|
{
|
|
CopyStringToBuffer(&Title,
|
|
" <span class=\"help_key unavailable\">c</span> <span class=\"help_text unavailable\">Credits</span>\n");
|
|
}
|
|
|
|
CopyStringToBuffer(&Title,
|
|
"\n"
|
|
" <h2>Movement</h2>\n"
|
|
" <div class=\"help_paragraph\">\n"
|
|
" <div class=\"key_block\">\n"
|
|
" <div class=\"key_column\" style=\"flex-grow: 1\">\n"
|
|
" <span class=\"help_key\">a</span>\n"
|
|
" </div>\n"
|
|
" <div class=\"key_column\" style=\"flex-grow: 2\">\n"
|
|
" <span class=\"help_key\">w</span><br>\n"
|
|
" <span class=\"help_key\">s</span>\n"
|
|
" </div>\n"
|
|
" <div class=\"key_column\" style=\"flex-grow: 1\">\n"
|
|
" <span class=\"help_key\">d</span>\n"
|
|
" </div>\n"
|
|
" </div>\n"
|
|
" <div class=\"key_block\">\n"
|
|
" <span class=\"help_key\">h</span>\n"
|
|
" <span class=\"help_key\">j</span>\n"
|
|
" <span class=\"help_key\">k</span>\n"
|
|
" <span class=\"help_key\">l</span>\n"
|
|
" </div>\n"
|
|
" <div class=\"key_block\">\n"
|
|
" <div style=\"flex-grow: 1\">\n"
|
|
" <span class=\"help_key\">←</span>\n"
|
|
" </div>\n"
|
|
" <div style=\"flex-grow: 2\">\n"
|
|
" <span class=\"help_key\">↑</span><br>\n"
|
|
" <span class=\"help_key\">↓</span>\n"
|
|
" </div>\n"
|
|
" <div style=\"flex-grow: 1\">\n"
|
|
" <span class=\"help_key\">→</span>\n"
|
|
" </div>\n"
|
|
" </div>\n"
|
|
" </div>\n"
|
|
" <br>\n");
|
|
|
|
if(HasQuoteMenu)
|
|
{
|
|
CopyStringToBuffer(&Title,
|
|
" <h2>Quotes ");
|
|
if(HasReferenceMenu)
|
|
{
|
|
CopyStringToBuffer(&Title, "and References Menus</h2>\n");
|
|
}
|
|
else
|
|
{
|
|
CopyStringToBuffer(&Title, "<span class=\"unavailable\">and References</span> Menus</h2>\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CopyStringToBuffer(&Title,
|
|
" <h2><span class=\"unavailable\">Quotes");
|
|
if(HasReferenceMenu)
|
|
{
|
|
CopyStringToBuffer(&Title, " and</span> References Menus</h2>\n");
|
|
}
|
|
else
|
|
{
|
|
CopyStringToBuffer(&Title, " and References Menus</span></h2>\n");
|
|
}
|
|
}
|
|
|
|
if(HasQuoteMenu || HasReferenceMenu)
|
|
{
|
|
CopyStringToBuffer(&Title,
|
|
" <span style=\"width: auto\" class=\"help_key\">Enter</span> <span class=\"help_text\">Jump to timecode</span><br>\n");
|
|
}
|
|
else
|
|
{
|
|
CopyStringToBuffer(&Title,
|
|
" <span style=\"width: auto\" class=\"help_key unavailable\">Enter</span> <span class=\"help_text unavailable\">Jump to timecode</span><br>\n");
|
|
}
|
|
|
|
if(HasReferenceMenu)
|
|
{
|
|
CopyStringToBuffer(&Title,
|
|
" <h2>References ");
|
|
if(HasCreditsMenu)
|
|
{
|
|
CopyStringToBuffer(&Title, "and Credits Menus</h2>\n");
|
|
}
|
|
else
|
|
{
|
|
CopyStringToBuffer(&Title, "<span class=\"unavailable\">and Credits</span> Menus</h2>\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CopyStringToBuffer(&Title,
|
|
" <h2><span class=\"unavailable\">References");
|
|
if(HasCreditsMenu)
|
|
{
|
|
CopyStringToBuffer(&Title, " and</span> Credits Menus</h2>\n");
|
|
}
|
|
else
|
|
{
|
|
CopyStringToBuffer(&Title, " and Credits Menus</span></h2>\n");
|
|
}
|
|
}
|
|
|
|
if(HasReferenceMenu || HasCreditsMenu)
|
|
{
|
|
CopyStringToBuffer(&Title,
|
|
" <span class=\"help_key\">o</span> <span class=\"help_text\">Open URL (in new tab)</span>\n");
|
|
}
|
|
else
|
|
{
|
|
CopyStringToBuffer(&Title,
|
|
" <span class=\"help_key unavailable\">o</span> <span class=\"help_text unavailable\">Open URL (in new tab)</span>\n");
|
|
}
|
|
|
|
CopyStringToBuffer(&Title,
|
|
"\n");
|
|
|
|
if(HasFilterMenu)
|
|
{
|
|
CopyStringToBuffer(&Title,
|
|
" <h2>Filter Menu</h2>\n"
|
|
" <span class=\"help_key\">x</span>, <span style=\"width: auto\" class=\"help_key\">Space</span> <span class=\"help_text\">Toggle category and focus next</span><br>\n"
|
|
" <span class=\"help_key\">X</span>, <span style=\"width: auto; margin-right: 0px\" class=\"help_key\">Shift</span><span style=\"width: auto\" class=\"help_key\">Space</span> <span class=\"help_text\">Toggle category and focus previous</span><br>\n"
|
|
" <span class=\"help_key\">v</span> <span class=\"help_text\">Invert topics / media as per focus</span>\n");
|
|
}
|
|
else
|
|
{
|
|
CopyStringToBuffer(&Title,
|
|
" <h2><span class=\"unavailable\">Filter Menu</span></h2>\n"
|
|
" <span class=\"help_key unavailable\">x</span>, <span style=\"width: auto\" class=\"help_key unavailable\">Space</span> <span class=\"help_text unavailable\">Toggle category and focus next</span><br>\n"
|
|
" <span class=\"help_key unavailable\">X</span>, <span style=\"width: auto; margin-right: 0px\" class=\"help_key unavailable\">Shift</span><span style=\"width: auto\" class=\"help_key unavailable\">Space</span> <span class=\"help_text unavailable\">Toggle category and focus previous</span><br>\n"
|
|
" <span class=\"help_key unavailable\">v</span> <span class=\"help_text unavailable\">Invert topics / media as per focus</span>\n");
|
|
}
|
|
|
|
if(HasCreditsMenu)
|
|
{
|
|
CopyStringToBuffer(&Title,
|
|
" <h2>Credits Menu</h2>\n"
|
|
" <span style=\"width: auto\" class=\"help_key\">Enter</span> <span class=\"help_text\">Open URL (in new tab)</span><br>\n");
|
|
}
|
|
else
|
|
{
|
|
CopyStringToBuffer(&Title,
|
|
" <h2><span class=\"unavailable\">Credits Menu</span></h2>\n"
|
|
" <span style=\"width: auto\" class=\"help_key unavailable\">Enter</span> <span class=\"help_text unavailable\">Open URL (in new tab)</span><br>\n");
|
|
}
|
|
|
|
CopyStringToBuffer(&Title,
|
|
" </div>\n"
|
|
" </div>\n"
|
|
|
|
" </div>\n");
|
|
|
|
CopyStringToBuffer(&Player,
|
|
" </div>\n"
|
|
" </div>\n");
|
|
|
|
//NOTE(matt): Collate the buffers!
|
|
#if DEBUG
|
|
printf("Buffer Collation\n\n");
|
|
#endif
|
|
|
|
CopyStringToBuffer(&Master,
|
|
"<html>\n"
|
|
" <head>\n"
|
|
" <meta charset=\"UTF-8\">\n"
|
|
" <title>");
|
|
CopyStringToBufferHTMLSafe(&Master, HMML.metadata.title);
|
|
CopyStringToBuffer(&Master, "</title>\n" // TODO(matt): Add the full name of the project, parsed from a config
|
|
"\n"
|
|
" <!-- Load the player -->\n"
|
|
" <script type=\"text/javascript\" src=\"player.js\"></script>\n"
|
|
" <link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\">\n"
|
|
" <link rel=\"stylesheet\" type=\"text/css\" href=\"%s.css\">\n"
|
|
" <link rel=\"stylesheet\" type=\"text/css\" href=\"topics.css\">\n"
|
|
" </head>\n"
|
|
" <body>\n",
|
|
HMML.metadata.project);
|
|
|
|
CopyStringToBuffer(&Script,
|
|
" <script>\n"
|
|
"\n"
|
|
"var menuState = [];\n");
|
|
|
|
CopyStringToBuffer(&Script,
|
|
"var quotesMenu = document.querySelector(\".quotes_container\");\n");
|
|
|
|
if(HasQuoteMenu)
|
|
{
|
|
CopyStringToBuffer(&Script,
|
|
"if(quotesMenu)\n"
|
|
"{\n"
|
|
"menuState.push(quotesMenu);\n"
|
|
"var quoteItems = quotesMenu.querySelectorAll(\".ref\");\n"
|
|
"for(var i = 0; i < quoteItems.length; ++i)\n"
|
|
"{\n"
|
|
" quoteItems[i].addEventListener(\"mouseenter\", function(ev) {\n"
|
|
" mouseOverQuotes(this);\n"
|
|
" })\n"
|
|
"};\n"
|
|
"var lastFocusedQuote = null;\n"
|
|
"}\n");
|
|
}
|
|
|
|
CopyStringToBuffer(&Script,
|
|
"var referencesMenu = document.querySelector(\".references_container\");\n");
|
|
|
|
if(HasReferenceMenu)
|
|
{
|
|
CopyStringToBuffer(&Script,
|
|
"if(referencesMenu)\n"
|
|
"{\n"
|
|
"menuState.push(referencesMenu);\n"
|
|
"var referenceItems = referencesMenu.querySelectorAll(\".ref\");\n"
|
|
"for(var i = 0; i < referenceItems.length; ++i)\n"
|
|
"{\n"
|
|
" referenceItems[i].addEventListener(\"mouseenter\", function(ev) {\n"
|
|
" mouseOverReferences(this);\n"
|
|
" })\n"
|
|
"};\n"
|
|
"var lastFocusedReference = null;\n"
|
|
"var lastFocusedIdentifier = null;\n"
|
|
"}\n");
|
|
}
|
|
|
|
CopyStringToBuffer(&Script,
|
|
"var filterMenu = document.querySelector(\".filter_container\");\n");
|
|
|
|
if(HasFilterMenu)
|
|
{
|
|
CopyStringToBuffer(&Script,
|
|
"if(filterMenu)\n"
|
|
"{\n"
|
|
" menuState.push(filterMenu);\n"
|
|
" var lastFocusedCategory = null;\n"
|
|
" var lastFocusedTopic = null;\n"
|
|
" var lastFocusedMedium = null;\n"
|
|
"\n"
|
|
" var filter = filterMenu.parentNode;\n"
|
|
"\n"
|
|
" var filterModeElement = filter.querySelector(\".filter_mode\");\n"
|
|
" filterModeElement.addEventListener(\"click\", function(ev) {\n"
|
|
" toggleFilterMode();\n"
|
|
" });\n"
|
|
"\n"
|
|
" var filterMode = filterModeElement.classList[1];\n"
|
|
" var filterItems = filter.querySelectorAll(\".filter_content\");\n"
|
|
" for(var i = 0; i < filterItems.length; ++i)\n"
|
|
" {\n"
|
|
" filterItems[i].addEventListener(\"mouseenter\", function(ev) {\n"
|
|
" navigateFilter(this);\n"
|
|
" })\n"
|
|
"\n"
|
|
" filterItems[i].addEventListener(\"click\", function(ev) {\n"
|
|
" filterItemToggle(this);\n"
|
|
" });\n"
|
|
"\n"
|
|
"%s\n"
|
|
"}\n"
|
|
"}\n", FilterState.Location);
|
|
}
|
|
|
|
CopyStringToBuffer(&Script,
|
|
"var creditsMenu = document.querySelector(\".credits_container\");\n");
|
|
|
|
if(HasCreditsMenu)
|
|
{
|
|
CopyStringToBuffer(&Script,
|
|
"if(creditsMenu)\n"
|
|
"{\n"
|
|
" menuState.push(creditsMenu);\n"
|
|
" var lastFocusedCreditItem = null;\n"
|
|
"\n"
|
|
" var creditItems = creditsMenu.querySelectorAll(\".person, .support\");\n"
|
|
" for(var i = 0; i < creditItems.length; ++i)\n"
|
|
" {\n"
|
|
" creditItems[i].addEventListener(\"mouseenter\", function(ev) {\n"
|
|
" if(this != lastFocusedCreditItem)\n"
|
|
" {\n"
|
|
" lastFocusedCreditItem.classList.remove(\"focused\");\n"
|
|
" lastFocusedCreditItem = this;\n"
|
|
" focusedElement = lastFocusedCreditItem;\n"
|
|
" focusedElement.classList.add(\"focused\");\n"
|
|
" }\n"
|
|
" })\n"
|
|
" }\n"
|
|
"}\n");
|
|
}
|
|
|
|
CopyStringToBuffer(&Script,
|
|
"var sourceMenus = document.querySelectorAll(\".menu\");\n"
|
|
"\n"
|
|
"var helpButton = document.querySelector(\".help\");\n"
|
|
"var helpDocumentation = helpButton.querySelector(\".help_container\");\n"
|
|
"helpButton.addEventListener(\"click\", function(ev) {\n"
|
|
" handleMouseOverMenu(this, ev.type);\n"
|
|
"})\n"
|
|
"\n"
|
|
"var focusedElement = null;\n"
|
|
"var focusedIdentifier = null;\n"
|
|
"\n"
|
|
"var player = new Player(document.querySelector(\".player_container\"), onRefChanged);\n"
|
|
"window.addEventListener(\"resize\", function() { player.updateSize(); });\n"
|
|
"document.addEventListener(\"keydown\", function(ev) {\n"
|
|
" var key = ev.key;\n"
|
|
" if(ev.getModifierState(\"Shift\") && key == \" \")\n"
|
|
" {\n"
|
|
" key = \"capitalSpace\";\n"
|
|
" }\n"
|
|
"\n"
|
|
" if(handleKey(key) == true && focusedElement)\n"
|
|
" {\n"
|
|
" ev.preventDefault();\n"
|
|
" }\n"
|
|
"});\n"
|
|
"\n"
|
|
"for(var i = 0; i < sourceMenus.length; ++i)\n"
|
|
"{\n"
|
|
" sourceMenus[i].addEventListener(\"mouseenter\", function(ev) {\n"
|
|
" handleMouseOverMenu(this, ev.type);\n"
|
|
" })\n"
|
|
" sourceMenus[i].addEventListener(\"mouseleave\", function(ev) {\n"
|
|
" handleMouseOverMenu(this, ev.type);\n"
|
|
" })\n"
|
|
"};\n"
|
|
"\n"
|
|
"var refTimecodes = document.querySelectorAll(\".refs .ref .timecode\");\n"
|
|
"for (var i = 0; i < refTimecodes.length; ++i) {\n"
|
|
" refTimecodes[i].addEventListener(\"click\", function(ev) {\n"
|
|
" if (player) {\n"
|
|
" var time = ev.currentTarget.getAttribute(\"data-timestamp\");\n"
|
|
" mouseSkipToTimecode(player, time, ev);\n"
|
|
" }\n"
|
|
" });\n"
|
|
"}\n"
|
|
"\n"
|
|
"var refSources = document.querySelectorAll(\".refs .ref\"); // This is for both quotes and refs\n"
|
|
"for (var i = 0; i < refSources.length; ++i) {\n"
|
|
" refSources[i].addEventListener(\"click\", function(ev) {\n"
|
|
" if (player) {\n"
|
|
" player.pause();\n"
|
|
" }\n"
|
|
" });\n"
|
|
"}\n"
|
|
"\n"
|
|
"var testMarkers = document.querySelectorAll(\".marker\");\n"
|
|
"\n"
|
|
"window.addEventListener(\"blur\", function(){\n"
|
|
" document.getElementById(\"focus-warn\").style.display = \"block\";\n"
|
|
"});\n"
|
|
"\n"
|
|
"window.addEventListener(\"focus\", function(){\n"
|
|
" document.getElementById(\"focus-warn\").style.display = \"none\";\n"
|
|
"});\n"
|
|
"\n"
|
|
"var colouredItems = document.querySelectorAll(\".author, .member, .project\");\n"
|
|
"for(i = 0; i < colouredItems.length; ++i)\n"
|
|
"{\n"
|
|
" setTextLightness(colouredItems[i]);\n"
|
|
"}\n"
|
|
"\n"
|
|
"var topicDots = document.querySelectorAll(\".category\");\n"
|
|
"for(var i = 0; i < topicDots.length; ++i)\n"
|
|
"{\n"
|
|
" setDotLightness(topicDots[i]);\n"
|
|
"}\n"
|
|
" </script>\n");
|
|
|
|
//NOTE(matt): Here is where we do all our CopyBuffer() calls
|
|
CopyBuffer(&Master, &Title);
|
|
CopyBuffer(&Master, &Player);
|
|
CopyBuffer(&Master, &Script);
|
|
//
|
|
|
|
// NOTE(matt): Tree structure of "global" buffer dependencies
|
|
// FilterState
|
|
// Script
|
|
// Annotation
|
|
// Colour
|
|
// Player
|
|
// AnnotatorInfo
|
|
// HostInfo
|
|
// CreditsMenu
|
|
// FilterMedia
|
|
// FilterTopics
|
|
// FilterMenu
|
|
// ReferenceMenu
|
|
// QuoteMenu
|
|
// Title
|
|
|
|
DeclaimBuffer(&FilterState, &ClaimedMemory);
|
|
DeclaimBuffer(&Script, &ClaimedMemory);
|
|
DeclaimBuffer(&Annotation, &ClaimedMemory);
|
|
DeclaimBuffer(&Colour, &ClaimedMemory);
|
|
DeclaimBuffer(&Player, &ClaimedMemory);
|
|
|
|
DeclaimBuffer(&AnnotatorInfo, &ClaimedMemory);
|
|
DeclaimBuffer(&HostInfo, &ClaimedMemory);
|
|
DeclaimBuffer(&CreditsMenu, &ClaimedMemory);
|
|
DeclaimBuffer(&FilterMedia, &ClaimedMemory);
|
|
DeclaimBuffer(&FilterTopics, &ClaimedMemory);
|
|
DeclaimBuffer(&FilterMenu, &ClaimedMemory);
|
|
DeclaimBuffer(&ReferenceMenu, &ClaimedMemory);
|
|
DeclaimBuffer(&QuoteMenu, &ClaimedMemory);
|
|
DeclaimBuffer(&Title, &ClaimedMemory);
|
|
|
|
CopyStringToBuffer(&Master,
|
|
" </body>\n"
|
|
"</html>\n");
|
|
|
|
FILE *OutFile;
|
|
if(!(OutFile = fopen("out.html", "w")))
|
|
{
|
|
perror(Args[0]);
|
|
hmml_free(&HMML);
|
|
free(MemoryArena);
|
|
return 1;
|
|
}
|
|
fwrite(Master.Location, Master.Ptr - Master.Location, 1, OutFile);
|
|
fclose(OutFile);
|
|
|
|
DeclaimBuffer(&Master, &ClaimedMemory);
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, "%s:%d: %s\n", Args[FileIndex], HMML.error.line, HMML.error.message);
|
|
}
|
|
hmml_free(&HMML);
|
|
}
|
|
free(MemoryArena);
|
|
}
|