From bcbf25bfbdd1f270ef2204567ee25c17b9890c72 Mon Sep 17 00:00:00 2001 From: elexis Date: Mon, 25 Jul 2016 09:07:45 +0000 Subject: [PATCH] Fix line endings of all files in source/ except source/third_party/. This was SVN commit r18558. --- source/collada/tests/actorviewer.bat | 4 +- source/lib/sysdep/os/osx/osx_pasteboard.mm | 190 +- source/lib/sysdep/os/win/aken/aken.c | 1052 +++---- .../lib/sysdep/os/win/aken/aken_install.bat | 172 +- source/lib/sysdep/os/win/aken/build_all.bat | 62 +- .../lib/sysdep/os/win/aken/build_single.bat | 134 +- source/lib/sysdep/os/win/aken/makefile | 14 +- source/lib/sysdep/os/win/aken/sources | 10 +- .../tools/atlas/AtlasFrontends/ActorEditor.rc | 2 +- .../tools/atlas/AtlasFrontends/_template.rc | 2 +- source/tools/atlas/AtlasFrontends/generate.pl | 44 +- .../VirtualDirTreeCtrl/README.txt | 4 +- .../VirtualDirTreeCtrl/file.xpm | 72 +- .../VirtualDirTreeCtrl/folder.xpm | 72 +- .../VirtualDirTreeCtrl/root.xpm | 72 +- source/tools/atlas/AtlasUI/Misc/atlas.rc | 14 +- source/tools/atlas/AtlasUI/TODO.txt | 186 +- source/tools/entgraph/entgraph.pl | 174 +- source/tools/entity/checkrefs.pl | 1162 +++---- source/tools/fontbuilder2/README.txt | 144 +- source/tools/fontbuilder2/charset.txt | 2716 ++++++++--------- .../tools/openlogsfolder/OpenLogsFolder.vbs | 10 +- source/tools/profiler2/Profiler2Report.js | 852 +++--- source/tools/profiler2/ReportDraw.js | 956 +++--- source/tools/profiler2/profiler2.html | 180 +- source/tools/profiler2/profiler2.js | 1004 +++--- source/tools/profiler2/utilities.js | 382 +-- .../userreport/templatetags/cycle.py | 198 +- 28 files changed, 4942 insertions(+), 4942 deletions(-) diff --git a/source/collada/tests/actorviewer.bat b/source/collada/tests/actorviewer.bat index c4bba29bf6..7b2fbc025f 100644 --- a/source/collada/tests/actorviewer.bat +++ b/source/collada/tests/actorviewer.bat @@ -1,2 +1,2 @@ -@cd ..\..\..\binaries\system -@pyrogenesis_dbg.exe -editor -actorviewer -mod=_test.minimal -mod=_test.collada +@cd ..\..\..\binaries\system +@pyrogenesis_dbg.exe -editor -actorviewer -mod=_test.minimal -mod=_test.collada diff --git a/source/lib/sysdep/os/osx/osx_pasteboard.mm b/source/lib/sysdep/os/osx/osx_pasteboard.mm index 1716637fa4..8808ec3e7c 100644 --- a/source/lib/sysdep/os/osx/osx_pasteboard.mm +++ b/source/lib/sysdep/os/osx/osx_pasteboard.mm @@ -1,95 +1,95 @@ -/* Copyright (c) 2013 Wildfire Games - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#import -#import // MAC_OS_X_VERSION_MIN_REQUIRED -#import -#import - -#import "osx_pasteboard.h" - -bool osx_GetStringFromPasteboard(std::string& out) -{ - NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; - NSString* string = nil; -#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 - // As of 10.6, pasteboards can hold multiple items - if ([pasteboard respondsToSelector: @selector(readObjectsForClasses:)]) - { - NSArray* classes = [NSArray arrayWithObjects:[NSString class], nil]; - NSDictionary* options = [NSDictionary dictionary]; - NSArray* copiedItems = [pasteboard readObjectsForClasses:classes options:options]; - // We only need to support a single item, so grab the first string - if (copiedItems != nil && [copiedItems count] > 0) - string = [copiedItems objectAtIndex:0]; - else - return false; // No strings found on pasteboard - } - else - { -#endif // fallback to 10.5 API - // Verify that there is a string available for us - NSArray* types = [NSArray arrayWithObjects:NSStringPboardType, nil]; - if ([pasteboard availableTypeFromArray:types] != nil) - string = [pasteboard stringForType:NSStringPboardType]; - else - return false; // No strings found on pasteboard -#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 - } -#endif - - if (string != nil) - out = std::string([string UTF8String]); - else - return false; // fail - - return true; // success -} - -bool osx_SendStringToPasteboard(const std::string& string) -{ - // We're only working with strings, so we don't need to lazily write - // anything (otherwise we'd need to set up an owner and data provider) - NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; - NSString* type; -#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 - if ([pasteboard respondsToSelector: @selector(clearContents)]) - { - type = NSPasteboardTypeString; - [pasteboard clearContents]; - } - else - { -#endif // fallback to 10.5 API - type = NSStringPboardType; - NSArray* types = [NSArray arrayWithObjects: type, nil]; - // Roughly equivalent to clearContents followed by addTypes:owner - [pasteboard declareTypes:types owner:nil]; -#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 - - } -#endif - - // May raise a NSPasteboardCommunicationException - BOOL ok = [pasteboard setString:[NSString stringWithUTF8String:string.c_str()] forType:type]; - return ok == YES; -} +/* Copyright (c) 2013 Wildfire Games + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import +#import // MAC_OS_X_VERSION_MIN_REQUIRED +#import +#import + +#import "osx_pasteboard.h" + +bool osx_GetStringFromPasteboard(std::string& out) +{ + NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; + NSString* string = nil; +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 + // As of 10.6, pasteboards can hold multiple items + if ([pasteboard respondsToSelector: @selector(readObjectsForClasses:)]) + { + NSArray* classes = [NSArray arrayWithObjects:[NSString class], nil]; + NSDictionary* options = [NSDictionary dictionary]; + NSArray* copiedItems = [pasteboard readObjectsForClasses:classes options:options]; + // We only need to support a single item, so grab the first string + if (copiedItems != nil && [copiedItems count] > 0) + string = [copiedItems objectAtIndex:0]; + else + return false; // No strings found on pasteboard + } + else + { +#endif // fallback to 10.5 API + // Verify that there is a string available for us + NSArray* types = [NSArray arrayWithObjects:NSStringPboardType, nil]; + if ([pasteboard availableTypeFromArray:types] != nil) + string = [pasteboard stringForType:NSStringPboardType]; + else + return false; // No strings found on pasteboard +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 + } +#endif + + if (string != nil) + out = std::string([string UTF8String]); + else + return false; // fail + + return true; // success +} + +bool osx_SendStringToPasteboard(const std::string& string) +{ + // We're only working with strings, so we don't need to lazily write + // anything (otherwise we'd need to set up an owner and data provider) + NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; + NSString* type; +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 + if ([pasteboard respondsToSelector: @selector(clearContents)]) + { + type = NSPasteboardTypeString; + [pasteboard clearContents]; + } + else + { +#endif // fallback to 10.5 API + type = NSStringPboardType; + NSArray* types = [NSArray arrayWithObjects: type, nil]; + // Roughly equivalent to clearContents followed by addTypes:owner + [pasteboard declareTypes:types owner:nil]; +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 + + } +#endif + + // May raise a NSPasteboardCommunicationException + BOOL ok = [pasteboard setString:[NSString stringWithUTF8String:string.c_str()] forType:type]; + return ok == YES; +} diff --git a/source/lib/sysdep/os/win/aken/aken.c b/source/lib/sysdep/os/win/aken/aken.c index 5f7f016d3a..d5b68235f1 100644 --- a/source/lib/sysdep/os/win/aken/aken.c +++ b/source/lib/sysdep/os/win/aken/aken.c @@ -1,526 +1,526 @@ -/* Copyright (c) 2010 Wildfire Games - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -// note: staticdv cannot yet check C++ code. - -#include -#include "aken.h" -#include "intrinsics.h" - -#define WIN32_NAME L"\\DosDevices\\Aken" -#define DEVICE_NAME L"\\Device\\Aken" - -// placate PREfast -DRIVER_INITIALIZE DriverEntry; -__drv_dispatchType(IRP_MJ_CREATE) DRIVER_DISPATCH AkenCreate; -__drv_dispatchType(IRP_MJ_CLOSE) DRIVER_DISPATCH AkenClose; -__drv_dispatchType(IRP_MJ_DEVICE_CONTROL) DRIVER_DISPATCH AkenDeviceControl; -DRIVER_UNLOAD AkenUnload; - -// this driver isn't large, but it's still slightly nicer to make its -// functions pageable and thus not waste precious non-paged pool. -// #pragma code_seg is more convenient than specifying alloc_text for -// every other function. -#pragma alloc_text(INIT, DriverEntry) // => discardable -#pragma code_seg(push, "PAGE") - - -//----------------------------------------------------------------------------- -// memory mapping -//----------------------------------------------------------------------------- - -/* -there are three approaches to mapping physical memory: -(http://www.microsoft.com/whdc/driver/kernel/mem-mgmt.mspx) - -- MmMapIoSpace (http://support.microsoft.com/kb/189327/en-us). despite the - name, it maps physical pages of any kind by allocating PTEs. very easy to - implement, but occupies precious kernel address space. possible bugs: - http://www.osronline.com/showThread.cfm?link=96737 - http://support.microsoft.com/kb/925793/en-us - -- ZwMapViewOfSection of PhysicalMemory (http://tinyurl.com/yozmgy). - the code is a bit bulky, but the WinXP API prevents mapping pages with - conflicting attributes (see below). - -- MmMapLockedPagesSpecifyCache or MmGetSystemAddressForMdlSafe - (http://www.osronline.com/article.cfm?id=423). note: the latter is a macro - that calls the former. this is the 'normal' and fully documented way, - but it doesn't appear able to map a fixed physical address. - (MmAllocatePagesForMdl understandably doesn't work since some pages we - want to map are marked as unavailable for allocation, and I don't see - another documented way to fill an MDL with PFNs.) - -our choice here is forced by a very insidious issue. if someone else has -already mapped a page with different attributes (e.g. cacheable), TLBs -may end up corrupted, leading to disaster. the search for a documented -means of accessing the page frame database (to check if mapped anywhere -and determine the previously set attributes) has not borne fruit, so we -must use ZwMapViewOfSection. (if taking this up again, see -http://www.woodmann.com/forum/archive/index.php/t-6516.html ) - -note that we guess if the page will have been mapped as cacheable and -even try the opposite if that turns out to have been incorrect. -*/ - -static int IsMemoryUncacheable(DWORD64 physicalAddress64) -{ - PAGED_CODE(); - - // original PC memory - contains BIOS - if(physicalAddress64 < 0x100000) - return 1; - - return 0; -} - -static NTSTATUS AkenMapPhysicalMemory(const DWORD64 physicalAddress64, const DWORD64 numBytes64, DWORD64* virtualAddress64) -{ - NTSTATUS ntStatus; - HANDLE hMemory; - LARGE_INTEGER physicalAddress; // convenience - physicalAddress.QuadPart = physicalAddress64; - - PAGED_CODE(); - - // get handle to PhysicalMemory object - { - OBJECT_ATTRIBUTES objectAttributes; - UNICODE_STRING objectName = RTL_CONSTANT_STRING(L"\\Device\\PhysicalMemory"); - const ULONG attributes = OBJ_CASE_INSENSITIVE; - const HANDLE rootDirectory = 0; - InitializeObjectAttributes(&objectAttributes, &objectName, attributes, rootDirectory, (PSECURITY_DESCRIPTOR)0); - ntStatus = ZwOpenSection(&hMemory, SECTION_ALL_ACCESS, &objectAttributes); - if(!NT_SUCCESS(ntStatus)) - { - KdPrint(("AkenMapPhysicalMemory: ZwOpenSection failed\n")); - return ntStatus; - } - } - - // add a reference (required to prevent the handle from being deleted) - { - PVOID physicalMemorySection = NULL; - const POBJECT_TYPE objectType = 0; // allowed since specifying KernelMode - ntStatus = ObReferenceObjectByHandle(hMemory, SECTION_ALL_ACCESS, objectType, KernelMode, &physicalMemorySection, 0); - if(!NT_SUCCESS(ntStatus)) - { - KdPrint(("AkenMapPhysicalMemory: ObReferenceObjectByHandle failed\n")); - goto close_handle; - } - } - - // note: mapmem.c does HalTranslateBusAddress, but we only care about - // system memory. translating doesn't appear to be necessary, even if - // much existing code uses it (probably due to cargo cult). - - // map desired memory into user PTEs - { - const HANDLE hProcess = (HANDLE)-1; - PVOID virtualBaseAddress = 0; // let ZwMapViewOfSection pick - const ULONG zeroBits = 0; // # high-order bits in address that must be 0 - SIZE_T mappedSize = (SIZE_T)numBytes64; // will receive the actual page-aligned size - LARGE_INTEGER physicalBaseAddress = physicalAddress; // will be rounded down to 64KB boundary - const SECTION_INHERIT inheritDisposition = ViewShare; - const ULONG allocationType = 0; - ULONG protect = PAGE_READWRITE; - if(IsMemoryUncacheable(physicalAddress64)) - protect |= PAGE_NOCACHE; - ntStatus = ZwMapViewOfSection(hMemory, hProcess, &virtualBaseAddress, zeroBits, mappedSize, &physicalBaseAddress, &mappedSize, inheritDisposition, allocationType, protect); - if(!NT_SUCCESS(ntStatus)) - { - // try again with the opposite cacheability attribute - protect ^= PAGE_NOCACHE; - ntStatus = ZwMapViewOfSection(hMemory, hProcess, &virtualBaseAddress, zeroBits, mappedSize, &physicalBaseAddress, &mappedSize, inheritDisposition, allocationType, protect); - if(!NT_SUCCESS(ntStatus)) - { - KdPrint(("AkenMapPhysicalMemory: ZwMapViewOfSection failed\n")); - goto close_handle; - } - } - - // the mapping rounded our physical base address down to the nearest - // 64KiB boundary, so adjust the virtual address accordingly. - { - const DWORD32 numBytesRoundedDown = physicalAddress.LowPart - physicalBaseAddress.LowPart; - ASSERT(numBytesRoundedDown < 0x10000); - *virtualAddress64 = (DWORD64)virtualBaseAddress + numBytesRoundedDown; - } - } - - ntStatus = STATUS_SUCCESS; - -close_handle: - // closing the handle even on success means that callers won't have to - // pass it back when unmapping. why does this work? ZwMapViewOfSection - // apparently adds a reference to hMemory. - ZwClose(hMemory); - - return ntStatus; -} - - -static NTSTATUS AkenUnmapPhysicalMemory(const DWORD64 virtualAddress) -{ - PAGED_CODE(); - - { - const HANDLE hProcess = (HANDLE)-1; - PVOID baseAddress = (PVOID)virtualAddress; - NTSTATUS ntStatus = ZwUnmapViewOfSection(hProcess, baseAddress); - if(!NT_SUCCESS(ntStatus)) - { - KdPrint(("AkenUnmapPhysicalMemory: ZwUnmapViewOfSection failed\n")); - return ntStatus; - } - } - - return STATUS_SUCCESS; -} - - -//----------------------------------------------------------------------------- -// helper functions called from DeviceControl -//----------------------------------------------------------------------------- - -static NTSTATUS AkenIoctlReadPort(PVOID buf, const ULONG inSize, ULONG* outSize) -{ - DWORD32 value; - - PAGED_CODE(); - - if(inSize != sizeof(AkenReadPortIn) || *outSize != sizeof(AkenReadPortOut)) - return STATUS_BUFFER_TOO_SMALL; - - { - const AkenReadPortIn* in = (const AkenReadPortIn*)buf; - const USHORT port = in->port; - const UCHAR numBytes = in->numBytes; - switch(numBytes) - { - case 1: - value = (DWORD32)READ_PORT_UCHAR((PUCHAR)port); - break; - case 2: - value = (DWORD32)READ_PORT_USHORT((PUSHORT)port); - break; - case 4: - value = (DWORD32)READ_PORT_ULONG((PULONG)port); - break; - default: - return STATUS_INVALID_PARAMETER; - } - } - - { - AkenReadPortOut* out = (AkenReadPortOut*)buf; - out->value = value; - } - return STATUS_SUCCESS; -} - -static NTSTATUS AkenIoctlWritePort(PVOID buf, const ULONG inSize, ULONG* outSize) -{ - PAGED_CODE(); - - if(inSize != sizeof(AkenWritePortIn) || *outSize != 0) - return STATUS_BUFFER_TOO_SMALL; - - { - const AkenWritePortIn* in = (const AkenWritePortIn*)buf; - const DWORD32 value = in->value; - const USHORT port = in->port; - const UCHAR numBytes = in->numBytes; - switch(numBytes) - { - case 1: - WRITE_PORT_UCHAR((PUCHAR)port, (UCHAR)(value & 0xFF)); - break; - case 2: - WRITE_PORT_USHORT((PUSHORT)port, (USHORT)(value & 0xFFFF)); - break; - case 4: - WRITE_PORT_ULONG((PULONG)port, value); - break; - default: - return STATUS_INVALID_PARAMETER; - } - } - - return STATUS_SUCCESS; -} - - -static NTSTATUS AkenIoctlMap(PVOID buf, const ULONG inSize, ULONG* outSize) -{ - DWORD64 virtualAddress; - NTSTATUS ntStatus; - - PAGED_CODE(); - - if(inSize != sizeof(AkenMapIn) || *outSize != sizeof(AkenMapOut)) - return STATUS_BUFFER_TOO_SMALL; - - { - const AkenMapIn* in = (const AkenMapIn*)buf; - const DWORD64 physicalAddress = in->physicalAddress; - const DWORD64 numBytes = in->numBytes; - ntStatus = AkenMapPhysicalMemory(physicalAddress, numBytes, &virtualAddress); - } - - { - AkenMapOut* out = (AkenMapOut*)buf; - out->virtualAddress = virtualAddress; - } - return ntStatus; -} - -static NTSTATUS AkenIoctlUnmap(PVOID buf, const ULONG inSize, ULONG* outSize) -{ - NTSTATUS ntStatus; - - PAGED_CODE(); - - if(inSize != sizeof(AkenUnmapIn) || *outSize != 0) - return STATUS_BUFFER_TOO_SMALL; - - { - const AkenUnmapIn* in = (const AkenUnmapIn*)buf; - const DWORD64 virtualAddress = in->virtualAddress; - ntStatus = AkenUnmapPhysicalMemory(virtualAddress); - } - - return ntStatus; -} - - -static NTSTATUS AkenIoctlReadModelSpecificRegister(PVOID buf, const ULONG inSize, ULONG* outSize) -{ - DWORD64 value; - - PAGED_CODE(); - - if(inSize != sizeof(AkenReadRegisterIn) || *outSize != sizeof(AkenReadRegisterOut)) - return STATUS_BUFFER_TOO_SMALL; - - { - const AkenReadRegisterIn* in = (const AkenReadRegisterIn*)buf; - const DWORD64 reg = in->reg; - value = __readmsr((int)reg); - } - - { - AkenReadRegisterOut* out = (AkenReadRegisterOut*)buf; - out->value = value; - } - - return STATUS_SUCCESS; -} - -static NTSTATUS AkenIoctlWriteModelSpecificRegister(PVOID buf, const ULONG inSize, ULONG* outSize) -{ - PAGED_CODE(); - - if(inSize != sizeof(AkenWriteRegisterIn) || *outSize != 0) - return STATUS_BUFFER_TOO_SMALL; - - { - const AkenWriteRegisterIn* in = (const AkenWriteRegisterIn*)buf; - const DWORD64 reg = in->reg; - const DWORD64 value = in->value; - __writemsr((unsigned long)reg, value); - } - - return STATUS_SUCCESS; -} - -static NTSTATUS AkenIoctlReadPerformanceMonitoringCounter(PVOID buf, const ULONG inSize, ULONG* outSize) -{ - DWORD64 value; - - PAGED_CODE(); - - if(inSize != sizeof(AkenReadRegisterIn) || *outSize != sizeof(AkenReadRegisterOut)) - return STATUS_BUFFER_TOO_SMALL; - - { - const AkenReadRegisterIn* in = (const AkenReadRegisterIn*)buf; - const DWORD64 reg = in->reg; - value = __readpmc((unsigned long)reg); - } - - { - AkenReadRegisterOut* out = (AkenReadRegisterOut*)buf; - out->value = value; - } - - return STATUS_SUCCESS; -} - - -static NTSTATUS AkenIoctlUnknown(PVOID buf, const ULONG inSize, ULONG* outSize) -{ - PAGED_CODE(); - - KdPrint(("AkenIoctlUnknown\n")); - - *outSize = 0; - return STATUS_INVALID_DEVICE_REQUEST; -} - - -typedef NTSTATUS (*AkenIoctl)(PVOID buf, ULONG inSize, ULONG* outSize); - -static AkenIoctl AkenIoctlFromCode(ULONG ioctlCode) -{ - PAGED_CODE(); - - switch(ioctlCode) - { - case IOCTL_AKEN_READ_PORT: - return AkenIoctlReadPort; - case IOCTL_AKEN_WRITE_PORT: - return AkenIoctlWritePort; - - case IOCTL_AKEN_MAP: - return AkenIoctlMap; - case IOCTL_AKEN_UNMAP: - return AkenIoctlUnmap; - - case IOCTL_AKEN_READ_MSR: - return AkenIoctlReadModelSpecificRegister; - case IOCTL_AKEN_WRITE_MSR: - return AkenIoctlWriteModelSpecificRegister; - - default: - return AkenIoctlUnknown; - } -} - - -//----------------------------------------------------------------------------- -// entry points -//----------------------------------------------------------------------------- - -static NTSTATUS AkenCreate(IN PDEVICE_OBJECT deviceObject, IN PIRP irp) -{ - PAGED_CODE(); - - irp->IoStatus.Status = STATUS_SUCCESS; - irp->IoStatus.Information = 0; - IoCompleteRequest(irp, IO_NO_INCREMENT); - return STATUS_SUCCESS; -} - - -static NTSTATUS AkenClose(IN PDEVICE_OBJECT deviceObject, IN PIRP irp) -{ - PAGED_CODE(); - - // same as AkenCreate ATM - irp->IoStatus.Status = STATUS_SUCCESS; - irp->IoStatus.Information = 0; - IoCompleteRequest(irp, IO_NO_INCREMENT); - return STATUS_SUCCESS; -} - - -static NTSTATUS AkenDeviceControl(IN PDEVICE_OBJECT deviceObject, IN PIRP irp) -{ - PAGED_CODE(); - - { - // get buffer from IRP. all our IOCTLs are METHOD_BUFFERED, so buf is - // allocated by the I/O manager and used for both input and output. - PVOID buf = irp->AssociatedIrp.SystemBuffer; - PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(irp); - ULONG ioctlCode = irpStack->Parameters.DeviceIoControl.IoControlCode; - const ULONG inSize = irpStack->Parameters.DeviceIoControl.InputBufferLength; - ULONG outSize = irpStack->Parameters.DeviceIoControl.OutputBufferLength; // modified by AkenIoctl* - - const AkenIoctl akenIoctl = AkenIoctlFromCode(ioctlCode); - const NTSTATUS ntStatus = akenIoctl(buf, inSize, &outSize); - - irp->IoStatus.Information = outSize; // number of bytes to copy from buf to user's buffer - irp->IoStatus.Status = ntStatus; - IoCompleteRequest(irp, IO_NO_INCREMENT); - return ntStatus; - } -} - - -static VOID AkenUnload(IN PDRIVER_OBJECT driverObject) -{ - PAGED_CODE(); - - KdPrint(("AkenUnload\n")); - - { - UNICODE_STRING win32Name = RTL_CONSTANT_STRING(WIN32_NAME); - IoDeleteSymbolicLink(&win32Name); - } - - if(driverObject->DeviceObject) - IoDeleteDevice(driverObject->DeviceObject); -} - - -#pragma code_seg(pop) // make sure we don't countermand the alloc_text - -NTSTATUS DriverEntry(IN PDRIVER_OBJECT driverObject, IN PUNICODE_STRING registryPath) -{ - UNICODE_STRING deviceName = RTL_CONSTANT_STRING(DEVICE_NAME); - - // create device object - PDEVICE_OBJECT deviceObject; - { - const ULONG deviceExtensionSize = 0; - const ULONG deviceCharacteristics = FILE_DEVICE_SECURE_OPEN; - const BOOLEAN exlusive = TRUE; - NTSTATUS ntStatus = IoCreateDevice(driverObject, deviceExtensionSize, &deviceName, FILE_DEVICE_AKEN, deviceCharacteristics, exlusive, &deviceObject); - if(!NT_SUCCESS(ntStatus)) - { - KdPrint(("DriverEntry: IoCreateDevice failed\n")); - return ntStatus; - } - } - - // set entry points - driverObject->MajorFunction[IRP_MJ_CREATE] = AkenCreate; - driverObject->MajorFunction[IRP_MJ_CLOSE] = AkenClose; - driverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = AkenDeviceControl; - driverObject->DriverUnload = AkenUnload; - - // symlink NT device name to Win32 namespace - { - UNICODE_STRING win32Name = RTL_CONSTANT_STRING(WIN32_NAME); - NTSTATUS ntStatus = IoCreateSymbolicLink(&win32Name, &deviceName); - if(!NT_SUCCESS(ntStatus)) - { - KdPrint(("DriverEntry: IoCreateSymbolicLink failed\n")); - IoDeleteDevice(deviceObject); - return ntStatus; - } - } - - return STATUS_SUCCESS; -} +/* Copyright (c) 2010 Wildfire Games + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +// note: staticdv cannot yet check C++ code. + +#include +#include "aken.h" +#include "intrinsics.h" + +#define WIN32_NAME L"\\DosDevices\\Aken" +#define DEVICE_NAME L"\\Device\\Aken" + +// placate PREfast +DRIVER_INITIALIZE DriverEntry; +__drv_dispatchType(IRP_MJ_CREATE) DRIVER_DISPATCH AkenCreate; +__drv_dispatchType(IRP_MJ_CLOSE) DRIVER_DISPATCH AkenClose; +__drv_dispatchType(IRP_MJ_DEVICE_CONTROL) DRIVER_DISPATCH AkenDeviceControl; +DRIVER_UNLOAD AkenUnload; + +// this driver isn't large, but it's still slightly nicer to make its +// functions pageable and thus not waste precious non-paged pool. +// #pragma code_seg is more convenient than specifying alloc_text for +// every other function. +#pragma alloc_text(INIT, DriverEntry) // => discardable +#pragma code_seg(push, "PAGE") + + +//----------------------------------------------------------------------------- +// memory mapping +//----------------------------------------------------------------------------- + +/* +there are three approaches to mapping physical memory: +(http://www.microsoft.com/whdc/driver/kernel/mem-mgmt.mspx) + +- MmMapIoSpace (http://support.microsoft.com/kb/189327/en-us). despite the + name, it maps physical pages of any kind by allocating PTEs. very easy to + implement, but occupies precious kernel address space. possible bugs: + http://www.osronline.com/showThread.cfm?link=96737 + http://support.microsoft.com/kb/925793/en-us + +- ZwMapViewOfSection of PhysicalMemory (http://tinyurl.com/yozmgy). + the code is a bit bulky, but the WinXP API prevents mapping pages with + conflicting attributes (see below). + +- MmMapLockedPagesSpecifyCache or MmGetSystemAddressForMdlSafe + (http://www.osronline.com/article.cfm?id=423). note: the latter is a macro + that calls the former. this is the 'normal' and fully documented way, + but it doesn't appear able to map a fixed physical address. + (MmAllocatePagesForMdl understandably doesn't work since some pages we + want to map are marked as unavailable for allocation, and I don't see + another documented way to fill an MDL with PFNs.) + +our choice here is forced by a very insidious issue. if someone else has +already mapped a page with different attributes (e.g. cacheable), TLBs +may end up corrupted, leading to disaster. the search for a documented +means of accessing the page frame database (to check if mapped anywhere +and determine the previously set attributes) has not borne fruit, so we +must use ZwMapViewOfSection. (if taking this up again, see +http://www.woodmann.com/forum/archive/index.php/t-6516.html ) + +note that we guess if the page will have been mapped as cacheable and +even try the opposite if that turns out to have been incorrect. +*/ + +static int IsMemoryUncacheable(DWORD64 physicalAddress64) +{ + PAGED_CODE(); + + // original PC memory - contains BIOS + if(physicalAddress64 < 0x100000) + return 1; + + return 0; +} + +static NTSTATUS AkenMapPhysicalMemory(const DWORD64 physicalAddress64, const DWORD64 numBytes64, DWORD64* virtualAddress64) +{ + NTSTATUS ntStatus; + HANDLE hMemory; + LARGE_INTEGER physicalAddress; // convenience + physicalAddress.QuadPart = physicalAddress64; + + PAGED_CODE(); + + // get handle to PhysicalMemory object + { + OBJECT_ATTRIBUTES objectAttributes; + UNICODE_STRING objectName = RTL_CONSTANT_STRING(L"\\Device\\PhysicalMemory"); + const ULONG attributes = OBJ_CASE_INSENSITIVE; + const HANDLE rootDirectory = 0; + InitializeObjectAttributes(&objectAttributes, &objectName, attributes, rootDirectory, (PSECURITY_DESCRIPTOR)0); + ntStatus = ZwOpenSection(&hMemory, SECTION_ALL_ACCESS, &objectAttributes); + if(!NT_SUCCESS(ntStatus)) + { + KdPrint(("AkenMapPhysicalMemory: ZwOpenSection failed\n")); + return ntStatus; + } + } + + // add a reference (required to prevent the handle from being deleted) + { + PVOID physicalMemorySection = NULL; + const POBJECT_TYPE objectType = 0; // allowed since specifying KernelMode + ntStatus = ObReferenceObjectByHandle(hMemory, SECTION_ALL_ACCESS, objectType, KernelMode, &physicalMemorySection, 0); + if(!NT_SUCCESS(ntStatus)) + { + KdPrint(("AkenMapPhysicalMemory: ObReferenceObjectByHandle failed\n")); + goto close_handle; + } + } + + // note: mapmem.c does HalTranslateBusAddress, but we only care about + // system memory. translating doesn't appear to be necessary, even if + // much existing code uses it (probably due to cargo cult). + + // map desired memory into user PTEs + { + const HANDLE hProcess = (HANDLE)-1; + PVOID virtualBaseAddress = 0; // let ZwMapViewOfSection pick + const ULONG zeroBits = 0; // # high-order bits in address that must be 0 + SIZE_T mappedSize = (SIZE_T)numBytes64; // will receive the actual page-aligned size + LARGE_INTEGER physicalBaseAddress = physicalAddress; // will be rounded down to 64KB boundary + const SECTION_INHERIT inheritDisposition = ViewShare; + const ULONG allocationType = 0; + ULONG protect = PAGE_READWRITE; + if(IsMemoryUncacheable(physicalAddress64)) + protect |= PAGE_NOCACHE; + ntStatus = ZwMapViewOfSection(hMemory, hProcess, &virtualBaseAddress, zeroBits, mappedSize, &physicalBaseAddress, &mappedSize, inheritDisposition, allocationType, protect); + if(!NT_SUCCESS(ntStatus)) + { + // try again with the opposite cacheability attribute + protect ^= PAGE_NOCACHE; + ntStatus = ZwMapViewOfSection(hMemory, hProcess, &virtualBaseAddress, zeroBits, mappedSize, &physicalBaseAddress, &mappedSize, inheritDisposition, allocationType, protect); + if(!NT_SUCCESS(ntStatus)) + { + KdPrint(("AkenMapPhysicalMemory: ZwMapViewOfSection failed\n")); + goto close_handle; + } + } + + // the mapping rounded our physical base address down to the nearest + // 64KiB boundary, so adjust the virtual address accordingly. + { + const DWORD32 numBytesRoundedDown = physicalAddress.LowPart - physicalBaseAddress.LowPart; + ASSERT(numBytesRoundedDown < 0x10000); + *virtualAddress64 = (DWORD64)virtualBaseAddress + numBytesRoundedDown; + } + } + + ntStatus = STATUS_SUCCESS; + +close_handle: + // closing the handle even on success means that callers won't have to + // pass it back when unmapping. why does this work? ZwMapViewOfSection + // apparently adds a reference to hMemory. + ZwClose(hMemory); + + return ntStatus; +} + + +static NTSTATUS AkenUnmapPhysicalMemory(const DWORD64 virtualAddress) +{ + PAGED_CODE(); + + { + const HANDLE hProcess = (HANDLE)-1; + PVOID baseAddress = (PVOID)virtualAddress; + NTSTATUS ntStatus = ZwUnmapViewOfSection(hProcess, baseAddress); + if(!NT_SUCCESS(ntStatus)) + { + KdPrint(("AkenUnmapPhysicalMemory: ZwUnmapViewOfSection failed\n")); + return ntStatus; + } + } + + return STATUS_SUCCESS; +} + + +//----------------------------------------------------------------------------- +// helper functions called from DeviceControl +//----------------------------------------------------------------------------- + +static NTSTATUS AkenIoctlReadPort(PVOID buf, const ULONG inSize, ULONG* outSize) +{ + DWORD32 value; + + PAGED_CODE(); + + if(inSize != sizeof(AkenReadPortIn) || *outSize != sizeof(AkenReadPortOut)) + return STATUS_BUFFER_TOO_SMALL; + + { + const AkenReadPortIn* in = (const AkenReadPortIn*)buf; + const USHORT port = in->port; + const UCHAR numBytes = in->numBytes; + switch(numBytes) + { + case 1: + value = (DWORD32)READ_PORT_UCHAR((PUCHAR)port); + break; + case 2: + value = (DWORD32)READ_PORT_USHORT((PUSHORT)port); + break; + case 4: + value = (DWORD32)READ_PORT_ULONG((PULONG)port); + break; + default: + return STATUS_INVALID_PARAMETER; + } + } + + { + AkenReadPortOut* out = (AkenReadPortOut*)buf; + out->value = value; + } + return STATUS_SUCCESS; +} + +static NTSTATUS AkenIoctlWritePort(PVOID buf, const ULONG inSize, ULONG* outSize) +{ + PAGED_CODE(); + + if(inSize != sizeof(AkenWritePortIn) || *outSize != 0) + return STATUS_BUFFER_TOO_SMALL; + + { + const AkenWritePortIn* in = (const AkenWritePortIn*)buf; + const DWORD32 value = in->value; + const USHORT port = in->port; + const UCHAR numBytes = in->numBytes; + switch(numBytes) + { + case 1: + WRITE_PORT_UCHAR((PUCHAR)port, (UCHAR)(value & 0xFF)); + break; + case 2: + WRITE_PORT_USHORT((PUSHORT)port, (USHORT)(value & 0xFFFF)); + break; + case 4: + WRITE_PORT_ULONG((PULONG)port, value); + break; + default: + return STATUS_INVALID_PARAMETER; + } + } + + return STATUS_SUCCESS; +} + + +static NTSTATUS AkenIoctlMap(PVOID buf, const ULONG inSize, ULONG* outSize) +{ + DWORD64 virtualAddress; + NTSTATUS ntStatus; + + PAGED_CODE(); + + if(inSize != sizeof(AkenMapIn) || *outSize != sizeof(AkenMapOut)) + return STATUS_BUFFER_TOO_SMALL; + + { + const AkenMapIn* in = (const AkenMapIn*)buf; + const DWORD64 physicalAddress = in->physicalAddress; + const DWORD64 numBytes = in->numBytes; + ntStatus = AkenMapPhysicalMemory(physicalAddress, numBytes, &virtualAddress); + } + + { + AkenMapOut* out = (AkenMapOut*)buf; + out->virtualAddress = virtualAddress; + } + return ntStatus; +} + +static NTSTATUS AkenIoctlUnmap(PVOID buf, const ULONG inSize, ULONG* outSize) +{ + NTSTATUS ntStatus; + + PAGED_CODE(); + + if(inSize != sizeof(AkenUnmapIn) || *outSize != 0) + return STATUS_BUFFER_TOO_SMALL; + + { + const AkenUnmapIn* in = (const AkenUnmapIn*)buf; + const DWORD64 virtualAddress = in->virtualAddress; + ntStatus = AkenUnmapPhysicalMemory(virtualAddress); + } + + return ntStatus; +} + + +static NTSTATUS AkenIoctlReadModelSpecificRegister(PVOID buf, const ULONG inSize, ULONG* outSize) +{ + DWORD64 value; + + PAGED_CODE(); + + if(inSize != sizeof(AkenReadRegisterIn) || *outSize != sizeof(AkenReadRegisterOut)) + return STATUS_BUFFER_TOO_SMALL; + + { + const AkenReadRegisterIn* in = (const AkenReadRegisterIn*)buf; + const DWORD64 reg = in->reg; + value = __readmsr((int)reg); + } + + { + AkenReadRegisterOut* out = (AkenReadRegisterOut*)buf; + out->value = value; + } + + return STATUS_SUCCESS; +} + +static NTSTATUS AkenIoctlWriteModelSpecificRegister(PVOID buf, const ULONG inSize, ULONG* outSize) +{ + PAGED_CODE(); + + if(inSize != sizeof(AkenWriteRegisterIn) || *outSize != 0) + return STATUS_BUFFER_TOO_SMALL; + + { + const AkenWriteRegisterIn* in = (const AkenWriteRegisterIn*)buf; + const DWORD64 reg = in->reg; + const DWORD64 value = in->value; + __writemsr((unsigned long)reg, value); + } + + return STATUS_SUCCESS; +} + +static NTSTATUS AkenIoctlReadPerformanceMonitoringCounter(PVOID buf, const ULONG inSize, ULONG* outSize) +{ + DWORD64 value; + + PAGED_CODE(); + + if(inSize != sizeof(AkenReadRegisterIn) || *outSize != sizeof(AkenReadRegisterOut)) + return STATUS_BUFFER_TOO_SMALL; + + { + const AkenReadRegisterIn* in = (const AkenReadRegisterIn*)buf; + const DWORD64 reg = in->reg; + value = __readpmc((unsigned long)reg); + } + + { + AkenReadRegisterOut* out = (AkenReadRegisterOut*)buf; + out->value = value; + } + + return STATUS_SUCCESS; +} + + +static NTSTATUS AkenIoctlUnknown(PVOID buf, const ULONG inSize, ULONG* outSize) +{ + PAGED_CODE(); + + KdPrint(("AkenIoctlUnknown\n")); + + *outSize = 0; + return STATUS_INVALID_DEVICE_REQUEST; +} + + +typedef NTSTATUS (*AkenIoctl)(PVOID buf, ULONG inSize, ULONG* outSize); + +static AkenIoctl AkenIoctlFromCode(ULONG ioctlCode) +{ + PAGED_CODE(); + + switch(ioctlCode) + { + case IOCTL_AKEN_READ_PORT: + return AkenIoctlReadPort; + case IOCTL_AKEN_WRITE_PORT: + return AkenIoctlWritePort; + + case IOCTL_AKEN_MAP: + return AkenIoctlMap; + case IOCTL_AKEN_UNMAP: + return AkenIoctlUnmap; + + case IOCTL_AKEN_READ_MSR: + return AkenIoctlReadModelSpecificRegister; + case IOCTL_AKEN_WRITE_MSR: + return AkenIoctlWriteModelSpecificRegister; + + default: + return AkenIoctlUnknown; + } +} + + +//----------------------------------------------------------------------------- +// entry points +//----------------------------------------------------------------------------- + +static NTSTATUS AkenCreate(IN PDEVICE_OBJECT deviceObject, IN PIRP irp) +{ + PAGED_CODE(); + + irp->IoStatus.Status = STATUS_SUCCESS; + irp->IoStatus.Information = 0; + IoCompleteRequest(irp, IO_NO_INCREMENT); + return STATUS_SUCCESS; +} + + +static NTSTATUS AkenClose(IN PDEVICE_OBJECT deviceObject, IN PIRP irp) +{ + PAGED_CODE(); + + // same as AkenCreate ATM + irp->IoStatus.Status = STATUS_SUCCESS; + irp->IoStatus.Information = 0; + IoCompleteRequest(irp, IO_NO_INCREMENT); + return STATUS_SUCCESS; +} + + +static NTSTATUS AkenDeviceControl(IN PDEVICE_OBJECT deviceObject, IN PIRP irp) +{ + PAGED_CODE(); + + { + // get buffer from IRP. all our IOCTLs are METHOD_BUFFERED, so buf is + // allocated by the I/O manager and used for both input and output. + PVOID buf = irp->AssociatedIrp.SystemBuffer; + PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(irp); + ULONG ioctlCode = irpStack->Parameters.DeviceIoControl.IoControlCode; + const ULONG inSize = irpStack->Parameters.DeviceIoControl.InputBufferLength; + ULONG outSize = irpStack->Parameters.DeviceIoControl.OutputBufferLength; // modified by AkenIoctl* + + const AkenIoctl akenIoctl = AkenIoctlFromCode(ioctlCode); + const NTSTATUS ntStatus = akenIoctl(buf, inSize, &outSize); + + irp->IoStatus.Information = outSize; // number of bytes to copy from buf to user's buffer + irp->IoStatus.Status = ntStatus; + IoCompleteRequest(irp, IO_NO_INCREMENT); + return ntStatus; + } +} + + +static VOID AkenUnload(IN PDRIVER_OBJECT driverObject) +{ + PAGED_CODE(); + + KdPrint(("AkenUnload\n")); + + { + UNICODE_STRING win32Name = RTL_CONSTANT_STRING(WIN32_NAME); + IoDeleteSymbolicLink(&win32Name); + } + + if(driverObject->DeviceObject) + IoDeleteDevice(driverObject->DeviceObject); +} + + +#pragma code_seg(pop) // make sure we don't countermand the alloc_text + +NTSTATUS DriverEntry(IN PDRIVER_OBJECT driverObject, IN PUNICODE_STRING registryPath) +{ + UNICODE_STRING deviceName = RTL_CONSTANT_STRING(DEVICE_NAME); + + // create device object + PDEVICE_OBJECT deviceObject; + { + const ULONG deviceExtensionSize = 0; + const ULONG deviceCharacteristics = FILE_DEVICE_SECURE_OPEN; + const BOOLEAN exlusive = TRUE; + NTSTATUS ntStatus = IoCreateDevice(driverObject, deviceExtensionSize, &deviceName, FILE_DEVICE_AKEN, deviceCharacteristics, exlusive, &deviceObject); + if(!NT_SUCCESS(ntStatus)) + { + KdPrint(("DriverEntry: IoCreateDevice failed\n")); + return ntStatus; + } + } + + // set entry points + driverObject->MajorFunction[IRP_MJ_CREATE] = AkenCreate; + driverObject->MajorFunction[IRP_MJ_CLOSE] = AkenClose; + driverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = AkenDeviceControl; + driverObject->DriverUnload = AkenUnload; + + // symlink NT device name to Win32 namespace + { + UNICODE_STRING win32Name = RTL_CONSTANT_STRING(WIN32_NAME); + NTSTATUS ntStatus = IoCreateSymbolicLink(&win32Name, &deviceName); + if(!NT_SUCCESS(ntStatus)) + { + KdPrint(("DriverEntry: IoCreateSymbolicLink failed\n")); + IoDeleteDevice(deviceObject); + return ntStatus; + } + } + + return STATUS_SUCCESS; +} diff --git a/source/lib/sysdep/os/win/aken/aken_install.bat b/source/lib/sysdep/os/win/aken/aken_install.bat index fde2b14683..efeab00cb3 100644 --- a/source/lib/sysdep/os/win/aken/aken_install.bat +++ b/source/lib/sysdep/os/win/aken/aken_install.bat @@ -1,87 +1,87 @@ -@ECHO OFF - -"%systemroot%\system32\cacls.exe" "%systemroot%\system32\config\system" >nul 2>&1 -IF ERRORLEVEL 1 GOTO relaunch - -REM detect whether OS is 32/64 bit -IF "%ProgramW6432%" == "%ProgramFiles%" ( - SET aken_bits=64 -) ELSE ( - SET aken_bits=32 -) - -IF "%1" == "enabletest" GOTO enabletest -IF "%1" == "disabletest" GOTO disabletest -IF "%1" == "install" GOTO install -IF "%1" == "remove" GOTO remove -GOTO usage - -:enabletest -bcdedit.exe /set TESTSIGNING ON -GOTO end - -:disabletest -bcdedit.exe /set TESTSIGNING OFF -GOTO end - -:install -IF (%2) == () ( - SET aken_path="%~p0\aken%aken_bits%.sys" -) ELSE ( - echo %2\aken%aken_bits%.sys - SET aken_path=%2\aken%aken_bits%.sys -) -echo %aken_path% -IF NOT EXIST %aken_path% GOTO notfound -sc create Aken DisplayName= Aken type= kernel start= auto binpath= %aken_path% -REM error= normal is default -IF ERRORLEVEL 1 GOTO failed -sc start Aken -IF ERRORLEVEL 1 GOTO failed -ECHO Success! -GOTO end - -:remove -sc stop Aken -sc delete Aken -IF ERRORLEVEL 1 GOTO failed -ECHO Success! (The previous line should read: [SC] DeleteService SUCCESS) -GOTO end - -:usage -ECHO To install the driver, please first enable test mode: -ECHO %0 enabletest -ECHO (This is necessary because Vista/Win7 x64 require signing with -ECHO a Microsoft "cross certificate". The Fraunhofer code signing certificate -ECHO is not enough, even though its chain of trust is impeccable. -ECHO Going the WHQL route, perhaps as an "unclassified" driver, might work. -ECHO see http://www.freeotfe.org/docs/Main/impact_of_kernel_driver_signing.htm ) -ECHO Then reboot (!) and install the driver: -ECHO %0 install ["path_to_directory_containing_aken*.sys"] -ECHO (If no path is given, we will use the directory of this batch file) -ECHO To remove the driver and disable test mode, execute the following: -ECHO %0 remove -ECHO %0 disabletest -PAUSE -GOTO end - -:relaunch -SET aken_vbs="%temp%\aken_run.vbs" -ECHO Set UAC = CreateObject^("Shell.Application"^) > %aken_vbs% -ECHO UAC.ShellExecute "cmd.exe", "/k %~s0 %1 %2", "", "runas", 1 >> %aken_vbs% -ECHO "To re-run this batch file as admin, we have created %aken_vbs% with the following contents:" -type %aken_vbs% -PAUSE -cscript //Nologo %aken_vbs% -DEL %aken_vbs% -GOTO end - -:notfound -ECHO Driver not found at specified path (%aken_path%) -GOTO end - -:failed -ECHO Something went wrong -- see previous line -GOTO end - +@ECHO OFF + +"%systemroot%\system32\cacls.exe" "%systemroot%\system32\config\system" >nul 2>&1 +IF ERRORLEVEL 1 GOTO relaunch + +REM detect whether OS is 32/64 bit +IF "%ProgramW6432%" == "%ProgramFiles%" ( + SET aken_bits=64 +) ELSE ( + SET aken_bits=32 +) + +IF "%1" == "enabletest" GOTO enabletest +IF "%1" == "disabletest" GOTO disabletest +IF "%1" == "install" GOTO install +IF "%1" == "remove" GOTO remove +GOTO usage + +:enabletest +bcdedit.exe /set TESTSIGNING ON +GOTO end + +:disabletest +bcdedit.exe /set TESTSIGNING OFF +GOTO end + +:install +IF (%2) == () ( + SET aken_path="%~p0\aken%aken_bits%.sys" +) ELSE ( + echo %2\aken%aken_bits%.sys + SET aken_path=%2\aken%aken_bits%.sys +) +echo %aken_path% +IF NOT EXIST %aken_path% GOTO notfound +sc create Aken DisplayName= Aken type= kernel start= auto binpath= %aken_path% +REM error= normal is default +IF ERRORLEVEL 1 GOTO failed +sc start Aken +IF ERRORLEVEL 1 GOTO failed +ECHO Success! +GOTO end + +:remove +sc stop Aken +sc delete Aken +IF ERRORLEVEL 1 GOTO failed +ECHO Success! (The previous line should read: [SC] DeleteService SUCCESS) +GOTO end + +:usage +ECHO To install the driver, please first enable test mode: +ECHO %0 enabletest +ECHO (This is necessary because Vista/Win7 x64 require signing with +ECHO a Microsoft "cross certificate". The Fraunhofer code signing certificate +ECHO is not enough, even though its chain of trust is impeccable. +ECHO Going the WHQL route, perhaps as an "unclassified" driver, might work. +ECHO see http://www.freeotfe.org/docs/Main/impact_of_kernel_driver_signing.htm ) +ECHO Then reboot (!) and install the driver: +ECHO %0 install ["path_to_directory_containing_aken*.sys"] +ECHO (If no path is given, we will use the directory of this batch file) +ECHO To remove the driver and disable test mode, execute the following: +ECHO %0 remove +ECHO %0 disabletest +PAUSE +GOTO end + +:relaunch +SET aken_vbs="%temp%\aken_run.vbs" +ECHO Set UAC = CreateObject^("Shell.Application"^) > %aken_vbs% +ECHO UAC.ShellExecute "cmd.exe", "/k %~s0 %1 %2", "", "runas", 1 >> %aken_vbs% +ECHO "To re-run this batch file as admin, we have created %aken_vbs% with the following contents:" +type %aken_vbs% +PAUSE +cscript //Nologo %aken_vbs% +DEL %aken_vbs% +GOTO end + +:notfound +ECHO Driver not found at specified path (%aken_path%) +GOTO end + +:failed +ECHO Something went wrong -- see previous line +GOTO end + :end \ No newline at end of file diff --git a/source/lib/sysdep/os/win/aken/build_all.bat b/source/lib/sysdep/os/win/aken/build_all.bat index c7a598e353..7166d0e37c 100644 --- a/source/lib/sysdep/os/win/aken/build_all.bat +++ b/source/lib/sysdep/os/win/aken/build_all.bat @@ -1,32 +1,32 @@ -@echo off -REM ensure header is up to date (simpler than setting include path) -copy ..\..\..\..\..\include\lib\sysdep\os\win\aken\aken.h - -cmd /c build_single.bat chk x64 -cmd /c build_single.bat fre x64 -cmd /c build_single.bat chk x86 -REM must come last because each build_single.bat deletes aken.sys, -REM and that is the final output name of this step -cmd /c build_single.bat fre x86 - -cd amd64 -copy /y aken64*.pdb ..\..\..\..\..\..\..\bin\x64 -copy /y aken64*.sys ..\..\..\..\..\..\..\bin\x64 -cd .. - -cd i386 -copy /y aken*.pdb ..\..\..\..\..\..\..\bin\Win32 -copy /y aken*.sys ..\..\..\..\..\..\..\bin\Win32 -cd .. - -echo outputs copied to bin directory; will delete ALL output files after pressing a key -pause -if exist amd64 (rmdir /S /Q amd64) -if exist i386 (rmdir /S /Q i386) -if exist objchk_wnet_amd64 (rmdir /S /Q objchk_wnet_amd64) -if exist objfre_wnet_amd64 (rmdir /S /Q objfre_wnet_amd64) -if exist objchk_wnet_x86 (rmdir /S /Q objchk_wnet_x86) -if exist objfre_wnet_x86 (rmdir /S /Q objfre_wnet_x86) -if exist *.log (del /Q *.log) -if exist *.err (del /Q *.err) +@echo off +REM ensure header is up to date (simpler than setting include path) +copy ..\..\..\..\..\include\lib\sysdep\os\win\aken\aken.h + +cmd /c build_single.bat chk x64 +cmd /c build_single.bat fre x64 +cmd /c build_single.bat chk x86 +REM must come last because each build_single.bat deletes aken.sys, +REM and that is the final output name of this step +cmd /c build_single.bat fre x86 + +cd amd64 +copy /y aken64*.pdb ..\..\..\..\..\..\..\bin\x64 +copy /y aken64*.sys ..\..\..\..\..\..\..\bin\x64 +cd .. + +cd i386 +copy /y aken*.pdb ..\..\..\..\..\..\..\bin\Win32 +copy /y aken*.sys ..\..\..\..\..\..\..\bin\Win32 +cd .. + +echo outputs copied to bin directory; will delete ALL output files after pressing a key +pause +if exist amd64 (rmdir /S /Q amd64) +if exist i386 (rmdir /S /Q i386) +if exist objchk_wnet_amd64 (rmdir /S /Q objchk_wnet_amd64) +if exist objfre_wnet_amd64 (rmdir /S /Q objfre_wnet_amd64) +if exist objchk_wnet_x86 (rmdir /S /Q objchk_wnet_x86) +if exist objfre_wnet_x86 (rmdir /S /Q objfre_wnet_x86) +if exist *.log (del /Q *.log) +if exist *.err (del /Q *.err) if exist *.wrn (del /Q *.wrn) \ No newline at end of file diff --git a/source/lib/sysdep/os/win/aken/build_single.bat b/source/lib/sysdep/os/win/aken/build_single.bat index 7cd6e7fc0c..00419f46d3 100644 --- a/source/lib/sysdep/os/win/aken/build_single.bat +++ b/source/lib/sysdep/os/win/aken/build_single.bat @@ -1,67 +1,67 @@ -@echo off -REM build a single configuration ({chk,fre} x {x64,x64}) -REM (must be in a separate file to allow invoking in a new cmd - otherwise, -REM setenv complains that the environment have already been set) -REM arguments: chk|fre x64|x86 -if %1==chk (goto configOK) -if %1==fre (goto configOK) -echo first parameter must be either chk or fre -goto :eof -:configOK -if %2==x64 (goto archOK) -if %2==x86 (goto archOK) -echo second parameter must be either x64 or x64 -goto :eof -:archOK - - -call C:\WinDDK\7600.16385.1\bin\setenv.bat C:\WinDDK\7600.16385.1\ %1 %2 WNET -e: -cd \FOM_Work\Programmierung\lowlevel\src\sysdep\os\win\aken - - -REM delete outputs to ensure they get rebuilt -if %2==x64 (goto delete64) else (goto delete32) - -:delete64 -if not exist amd64 (goto deleteEnd) -cd amd64 -if exist aken.sys (del /Q aken.sys) -if exist aken.pdb (del /Q aken.pdb) -cd .. -goto deleteEnd - -:delete32 -if not exist i386 (goto deleteEnd) -cd i386 -if exist aken.sys (del /Q aken.sys) -if exist aken.pdb (del /Q aken.pdb) -cd .. -goto deleteEnd - -:deleteEnd - - -build - - -REM rename outputs in preparation for build_all's copying them to the binaries directories -if %2==x64 (goto rename64) else (goto rename32) - -:rename64 -if not exist amd64 (goto renameEnd) -cd amd64 -if %1==chk (ren aken.pdb aken64d.pdb) else (ren aken.pdb aken64.pdb) -if %1==chk (ren aken.sys aken64d.sys) else (ren aken.sys aken64.sys) -cd .. -goto renameEnd - -:rename32 -if not exist i386 (goto renameEnd) -cd i386 -if %1==chk (ren aken.pdb akend.pdb) -if %1==chk (ren aken.sys akend.sys) -cd .. -goto renameEnd - -:renameEnd +@echo off +REM build a single configuration ({chk,fre} x {x64,x64}) +REM (must be in a separate file to allow invoking in a new cmd - otherwise, +REM setenv complains that the environment have already been set) +REM arguments: chk|fre x64|x86 +if %1==chk (goto configOK) +if %1==fre (goto configOK) +echo first parameter must be either chk or fre +goto :eof +:configOK +if %2==x64 (goto archOK) +if %2==x86 (goto archOK) +echo second parameter must be either x64 or x64 +goto :eof +:archOK + + +call C:\WinDDK\7600.16385.1\bin\setenv.bat C:\WinDDK\7600.16385.1\ %1 %2 WNET +e: +cd \FOM_Work\Programmierung\lowlevel\src\sysdep\os\win\aken + + +REM delete outputs to ensure they get rebuilt +if %2==x64 (goto delete64) else (goto delete32) + +:delete64 +if not exist amd64 (goto deleteEnd) +cd amd64 +if exist aken.sys (del /Q aken.sys) +if exist aken.pdb (del /Q aken.pdb) +cd .. +goto deleteEnd + +:delete32 +if not exist i386 (goto deleteEnd) +cd i386 +if exist aken.sys (del /Q aken.sys) +if exist aken.pdb (del /Q aken.pdb) +cd .. +goto deleteEnd + +:deleteEnd + + +build + + +REM rename outputs in preparation for build_all's copying them to the binaries directories +if %2==x64 (goto rename64) else (goto rename32) + +:rename64 +if not exist amd64 (goto renameEnd) +cd amd64 +if %1==chk (ren aken.pdb aken64d.pdb) else (ren aken.pdb aken64.pdb) +if %1==chk (ren aken.sys aken64d.sys) else (ren aken.sys aken64.sys) +cd .. +goto renameEnd + +:rename32 +if not exist i386 (goto renameEnd) +cd i386 +if %1==chk (ren aken.pdb akend.pdb) +if %1==chk (ren aken.sys akend.sys) +cd .. +goto renameEnd + +:renameEnd diff --git a/source/lib/sysdep/os/win/aken/makefile b/source/lib/sysdep/os/win/aken/makefile index 9c985f57bc..58189757d6 100644 --- a/source/lib/sysdep/os/win/aken/makefile +++ b/source/lib/sysdep/os/win/aken/makefile @@ -1,7 +1,7 @@ -# -# DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source -# file to this component. This file merely indirects to the real make file -# that is shared by all the driver components of the Windows NT DDK -# - -!INCLUDE $(NTMAKEENV)\makefile.def +# +# DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source +# file to this component. This file merely indirects to the real make file +# that is shared by all the driver components of the Windows NT DDK +# + +!INCLUDE $(NTMAKEENV)\makefile.def diff --git a/source/lib/sysdep/os/win/aken/sources b/source/lib/sysdep/os/win/aken/sources index ce0b5aa055..bc44aa3aa4 100644 --- a/source/lib/sysdep/os/win/aken/sources +++ b/source/lib/sysdep/os/win/aken/sources @@ -1,5 +1,5 @@ -TARGETNAME=aken -TARGETPATH=. -TARGETTYPE=DRIVER - -SOURCES=aken.c +TARGETNAME=aken +TARGETPATH=. +TARGETTYPE=DRIVER + +SOURCES=aken.c diff --git a/source/tools/atlas/AtlasFrontends/ActorEditor.rc b/source/tools/atlas/AtlasFrontends/ActorEditor.rc index 3f54092011..2eba60f3b7 100644 --- a/source/tools/atlas/AtlasFrontends/ActorEditor.rc +++ b/source/tools/atlas/AtlasFrontends/ActorEditor.rc @@ -1 +1 @@ -IDI_ICON1 ICON "..\\AtlasUI\\Misc\\Graphics\\ActorEditor.ico" +IDI_ICON1 ICON "..\\AtlasUI\\Misc\\Graphics\\ActorEditor.ico" diff --git a/source/tools/atlas/AtlasFrontends/_template.rc b/source/tools/atlas/AtlasFrontends/_template.rc index a91a0d1a40..32ed3eaff9 100644 --- a/source/tools/atlas/AtlasFrontends/_template.rc +++ b/source/tools/atlas/AtlasFrontends/_template.rc @@ -1 +1 @@ -IDI_ICON1 ICON "..\\AtlasUI\\Misc\\Graphics\\$$PROJECT_NAME$$.ico" +IDI_ICON1 ICON "..\\AtlasUI\\Misc\\Graphics\\$$PROJECT_NAME$$.ico" diff --git a/source/tools/atlas/AtlasFrontends/generate.pl b/source/tools/atlas/AtlasFrontends/generate.pl index 611ac30de2..1afc04a42e 100644 --- a/source/tools/atlas/AtlasFrontends/generate.pl +++ b/source/tools/atlas/AtlasFrontends/generate.pl @@ -1,22 +1,22 @@ -use strict; -use warnings; - -my @files = qw( .cpp .rc ); - -my @progs = ( - { - PROJECT_NAME => "ActorEditor", - WINDOW_NAME => "ActorEditor", - }, -); - -for my $p (@progs) { - for my $f (@files) { - open IN, "<", "_template$f" or die "Error opening _template$f: $!"; - open OUT, ">", "$p->{PROJECT_NAME}$f" or die "Error opening $p->{PROJECT_NAME}$f: $!"; - while () { - s/\$\$([A-Z_]+)\$\$/ $p->{$1} /eg; - print OUT; - } - } -} +use strict; +use warnings; + +my @files = qw( .cpp .rc ); + +my @progs = ( + { + PROJECT_NAME => "ActorEditor", + WINDOW_NAME => "ActorEditor", + }, +); + +for my $p (@progs) { + for my $f (@files) { + open IN, "<", "_template$f" or die "Error opening _template$f: $!"; + open OUT, ">", "$p->{PROJECT_NAME}$f" or die "Error opening $p->{PROJECT_NAME}$f: $!"; + while () { + s/\$\$([A-Z_]+)\$\$/ $p->{$1} /eg; + print OUT; + } + } +} diff --git a/source/tools/atlas/AtlasUI/CustomControls/VirtualDirTreeCtrl/README.txt b/source/tools/atlas/AtlasUI/CustomControls/VirtualDirTreeCtrl/README.txt index 773acc687e..81ae10f550 100644 --- a/source/tools/atlas/AtlasUI/CustomControls/VirtualDirTreeCtrl/README.txt +++ b/source/tools/atlas/AtlasUI/CustomControls/VirtualDirTreeCtrl/README.txt @@ -1,3 +1,3 @@ -wxVirtualDirTreeCtrl (v1.0b) from http://www.solidsteel.nl/jorg/components/virtualdirtreectrl/wxVirtualDirTreeCtrl.php - +wxVirtualDirTreeCtrl (v1.0b) from http://www.solidsteel.nl/jorg/components/virtualdirtreectrl/wxVirtualDirTreeCtrl.php + "wxVirtualDirTreeCtrl is freeware and distributed under the wxWidgets license." \ No newline at end of file diff --git a/source/tools/atlas/AtlasUI/CustomControls/VirtualDirTreeCtrl/file.xpm b/source/tools/atlas/AtlasUI/CustomControls/VirtualDirTreeCtrl/file.xpm index 71e49d00ae..71c9005939 100644 --- a/source/tools/atlas/AtlasUI/CustomControls/VirtualDirTreeCtrl/file.xpm +++ b/source/tools/atlas/AtlasUI/CustomControls/VirtualDirTreeCtrl/file.xpm @@ -1,36 +1,36 @@ -/* XPM */ -static const char *xpm_file[] = { -"16 16 16 2", -"00 c black", -"01 c #848484", -"02 c #D6D6CE", -"03 c gray100", -"04 c none", -"05 c gray100", -"06 c gray100", -"07 c gray100", -"08 c gray100", -"09 c gray100", -"10 c gray100", -"11 c gray100", -"12 c gray100", -"13 c gray100", -"14 c gray100", -"15 c gray100", -"04040404040404040404040404040404", -"04040101010101010101010104040404", -"04040103030303030303030001040404", -"04040103030303030303030002010404", -"04040103030000000000030000000004", -"04040103030303030303030303030004", -"04040103030000000000000003030004", -"04040103030303030303030303030004", -"04040103030000000000000003030004", -"04040103030303030303030303030004", -"04040103030000000000000003030004", -"04040103030303030303030303030004", -"04040103030000000000000003030004", -"04040103030303030303030303030004", -"04040103030303030303030303030004", -"04040000000000000000000000000004" -}; +/* XPM */ +static const char *xpm_file[] = { +"16 16 16 2", +"00 c black", +"01 c #848484", +"02 c #D6D6CE", +"03 c gray100", +"04 c none", +"05 c gray100", +"06 c gray100", +"07 c gray100", +"08 c gray100", +"09 c gray100", +"10 c gray100", +"11 c gray100", +"12 c gray100", +"13 c gray100", +"14 c gray100", +"15 c gray100", +"04040404040404040404040404040404", +"04040101010101010101010104040404", +"04040103030303030303030001040404", +"04040103030303030303030002010404", +"04040103030000000000030000000004", +"04040103030303030303030303030004", +"04040103030000000000000003030004", +"04040103030303030303030303030004", +"04040103030000000000000003030004", +"04040103030303030303030303030004", +"04040103030000000000000003030004", +"04040103030303030303030303030004", +"04040103030000000000000003030004", +"04040103030303030303030303030004", +"04040103030303030303030303030004", +"04040000000000000000000000000004" +}; diff --git a/source/tools/atlas/AtlasUI/CustomControls/VirtualDirTreeCtrl/folder.xpm b/source/tools/atlas/AtlasUI/CustomControls/VirtualDirTreeCtrl/folder.xpm index af2f96e4a6..b3363cbd79 100644 --- a/source/tools/atlas/AtlasUI/CustomControls/VirtualDirTreeCtrl/folder.xpm +++ b/source/tools/atlas/AtlasUI/CustomControls/VirtualDirTreeCtrl/folder.xpm @@ -1,36 +1,36 @@ -/* XPM */ -static const char *xpm_folder[] = { -"16 16 16 2", -"00 c #A06B04", -"01 c #B17B14", -"02 c #C18C25", -"03 c #CE9831", -"04 c gray29", -"05 c #777777", -"06 c gray55", -"07 c none", -"08 c #ECB94F", -"09 c #FFE178", -"10 c #FFF388", -"11 c #FFFB98", -"12 c #CECECE", -"13 c #DADADA", -"14 c gray97", -"15 c gray100", -"07070707070707070707070707070707", -"07070303030313140707070707070707", -"07031515151502121407070707070707", -"03151111111115020202010101131407", -"03111010101010151515151501061307", -"03100902020202020202020201010113", -"03090215151515151515151509150006", -"02090311111111111111111108110005", -"02090311111111111111111108110005", -"02090211101010101010101008110005", -"02090211090909090909090908110005", -"02090211090909090909090903110005", -"13010101010101000000000000000406", -"14120605050505050505050505050612", -"07141313131313131313131313131314", -"07070707070707070707070707070707" -}; +/* XPM */ +static const char *xpm_folder[] = { +"16 16 16 2", +"00 c #A06B04", +"01 c #B17B14", +"02 c #C18C25", +"03 c #CE9831", +"04 c gray29", +"05 c #777777", +"06 c gray55", +"07 c none", +"08 c #ECB94F", +"09 c #FFE178", +"10 c #FFF388", +"11 c #FFFB98", +"12 c #CECECE", +"13 c #DADADA", +"14 c gray97", +"15 c gray100", +"07070707070707070707070707070707", +"07070303030313140707070707070707", +"07031515151502121407070707070707", +"03151111111115020202010101131407", +"03111010101010151515151501061307", +"03100902020202020202020201010113", +"03090215151515151515151509150006", +"02090311111111111111111108110005", +"02090311111111111111111108110005", +"02090211101010101010101008110005", +"02090211090909090909090908110005", +"02090211090909090909090903110005", +"13010101010101000000000000000406", +"14120605050505050505050505050612", +"07141313131313131313131313131314", +"07070707070707070707070707070707" +}; diff --git a/source/tools/atlas/AtlasUI/CustomControls/VirtualDirTreeCtrl/root.xpm b/source/tools/atlas/AtlasUI/CustomControls/VirtualDirTreeCtrl/root.xpm index 60ad1f7cdd..b5f23a6c39 100644 --- a/source/tools/atlas/AtlasUI/CustomControls/VirtualDirTreeCtrl/root.xpm +++ b/source/tools/atlas/AtlasUI/CustomControls/VirtualDirTreeCtrl/root.xpm @@ -1,36 +1,36 @@ -/* XPM */ -static const char *xpm_root[] = { -"16 16 16 2", -"00 c #976911", -"01 c gray42", -"02 c #C18B24", -"03 c #E3B552", -"04 c #6482B3", -"05 c #8E9CAA", -"06 c #FFEE86", -"07 c gray74", -"08 c #68BDFF", -"09 c #B9CDE6", -"10 c #D2D2D2", -"11 c #D8E9F1", -"12 c #E7F7FF", -"13 c #F3F7FB", -"14 c #F7FFFF", -"15 c none", -"15151515040404040404040811131515", -"15151515041313131313130808101315", -"15150202041309090909130808081013", -"15021313041313131313131111040111", -"02130606041209090909090912040110", -"02060606041212121212121212040110", -"02060202040707070707070707040207", -"02060213111313131313131313110001", -"02060206071109090909090911070001", -"02030206071111111111111111070001", -"02030206071111111111111111070001", -"02030206070707070707070707070001", -"02030206060303030303030303060001", -"11020202020000000000000000000101", -"13100101010101010101010101010110", -"15131110101010101010101010101113" -}; +/* XPM */ +static const char *xpm_root[] = { +"16 16 16 2", +"00 c #976911", +"01 c gray42", +"02 c #C18B24", +"03 c #E3B552", +"04 c #6482B3", +"05 c #8E9CAA", +"06 c #FFEE86", +"07 c gray74", +"08 c #68BDFF", +"09 c #B9CDE6", +"10 c #D2D2D2", +"11 c #D8E9F1", +"12 c #E7F7FF", +"13 c #F3F7FB", +"14 c #F7FFFF", +"15 c none", +"15151515040404040404040811131515", +"15151515041313131313130808101315", +"15150202041309090909130808081013", +"15021313041313131313131111040111", +"02130606041209090909090912040110", +"02060606041212121212121212040110", +"02060202040707070707070707040207", +"02060213111313131313131313110001", +"02060206071109090909090911070001", +"02030206071111111111111111070001", +"02030206071111111111111111070001", +"02030206070707070707070707070001", +"02030206060303030303030303060001", +"11020202020000000000000000000101", +"13100101010101010101010101010110", +"15131110101010101010101010101113" +}; diff --git a/source/tools/atlas/AtlasUI/Misc/atlas.rc b/source/tools/atlas/AtlasUI/Misc/atlas.rc index d2ce4df65d..fa76c4270e 100644 --- a/source/tools/atlas/AtlasUI/Misc/atlas.rc +++ b/source/tools/atlas/AtlasUI/Misc/atlas.rc @@ -1,7 +1,7 @@ -ICON_ActorEditor ICON "Graphics\\ActorEditor.ico" -ICON_ArchiveViewer ICON "Graphics\\ArchiveViewer.ico" -ICON_FileConverter ICON "Graphics\\FileConverter.ico" -ICON_ScenarioEditor ICON "Graphics\\ScenarioEditor.ico" - -#define wxUSE_NO_MANIFEST 1 -#include "wx/msw/wx.rc" +ICON_ActorEditor ICON "Graphics\\ActorEditor.ico" +ICON_ArchiveViewer ICON "Graphics\\ArchiveViewer.ico" +ICON_FileConverter ICON "Graphics\\FileConverter.ico" +ICON_ScenarioEditor ICON "Graphics\\ScenarioEditor.ico" + +#define wxUSE_NO_MANIFEST 1 +#include "wx/msw/wx.rc" diff --git a/source/tools/atlas/AtlasUI/TODO.txt b/source/tools/atlas/AtlasUI/TODO.txt index 413aa1b39b..e27868bc89 100644 --- a/source/tools/atlas/AtlasUI/TODO.txt +++ b/source/tools/atlas/AtlasUI/TODO.txt @@ -1,93 +1,93 @@ -wxWidgets: -* alt+f with notebook tab focussed -* wxLogTrace "%s" wxGetMessage (msw/window.cpp) - -============================== - -Colour tester: - -============================== - -Actor editor: - -* Fix new actor fields - - -============================== - -General and/or unsorted miscellany: - -* Regression testing - -* Open actors from prop-actor selection - -* Make import undo work with multi-control windows, and update the title bar - -=== - -* Better input controls (=> export nicely, support undo, etc) - -* Help (tooltips, etc) - -* More efficient Datafile::ReadList (don't read from disk every time) - -* Version number - -* Better colouring in test_dude.xml, where some variants have no data - -* Tab to move between fields - -=== - -* Test/fix when running with large fonts, or weird XP styles - -* Better copy and paste (e.g. between multiple instances of the program, - and paste XML into other text-editing programs - -* Save on exit: don't ask if no changes - -* Browse for meshes/actors/etc, in a more nice/correct/unbuggy way - -* Window size memory - -* Column width memory - -* Allow reset memory - -* Input validation? - -* Browse for meshes/actors/etc, with mods - -* Customised AtObj->string conversion - -* Undo in text-editing boxes - -* AtlasObjectXML error handling - -* Use standard wxWidgets 2.5.4 release - -* Fix Escape in combo boxes inside dialogs - -* Make wxFileHistory work when not at the very end of a menu - -=== - -* Document lots - -* wxListItemAttr* OnGetItemAttr(long item) >>>const<<< - wx documentation wrong? - -* Better column widths / window sizes - -* Don't create a row when editing a blank one then removing focus without typing anything - -====== - -Done: (newest at top) - -* Display DDS info (compression, size) -* Copy and paste -* 'Create entity' button (take name, parent) -* Correct undo menu entry when selecting 'New' (so it's not 'Import') -* MRU file list -* 'New' menu item -* Import/export filter, for validation(?) and for handling attributes +wxWidgets: +* alt+f with notebook tab focussed +* wxLogTrace "%s" wxGetMessage (msw/window.cpp) + +============================== + +Colour tester: + +============================== + +Actor editor: + +* Fix new actor fields + + +============================== + +General and/or unsorted miscellany: + +* Regression testing + +* Open actors from prop-actor selection + +* Make import undo work with multi-control windows, and update the title bar + +=== + +* Better input controls (=> export nicely, support undo, etc) + +* Help (tooltips, etc) + +* More efficient Datafile::ReadList (don't read from disk every time) + +* Version number + +* Better colouring in test_dude.xml, where some variants have no data + +* Tab to move between fields + +=== + +* Test/fix when running with large fonts, or weird XP styles + +* Better copy and paste (e.g. between multiple instances of the program, + and paste XML into other text-editing programs + +* Save on exit: don't ask if no changes + +* Browse for meshes/actors/etc, in a more nice/correct/unbuggy way + +* Window size memory + +* Column width memory + +* Allow reset memory + +* Input validation? + +* Browse for meshes/actors/etc, with mods + +* Customised AtObj->string conversion + +* Undo in text-editing boxes + +* AtlasObjectXML error handling + +* Use standard wxWidgets 2.5.4 release + +* Fix Escape in combo boxes inside dialogs + +* Make wxFileHistory work when not at the very end of a menu + +=== + +* Document lots + +* wxListItemAttr* OnGetItemAttr(long item) >>>const<<< - wx documentation wrong? + +* Better column widths / window sizes + +* Don't create a row when editing a blank one then removing focus without typing anything + +====== + +Done: (newest at top) + +* Display DDS info (compression, size) +* Copy and paste +* 'Create entity' button (take name, parent) +* Correct undo menu entry when selecting 'New' (so it's not 'Import') +* MRU file list +* 'New' menu item +* Import/export filter, for validation(?) and for handling attributes diff --git a/source/tools/entgraph/entgraph.pl b/source/tools/entgraph/entgraph.pl index 8348de4007..206ccc83b0 100644 --- a/source/tools/entgraph/entgraph.pl +++ b/source/tools/entgraph/entgraph.pl @@ -1,88 +1,88 @@ -use strict; -use warnings; - -use File::Find; - -my $dir = '../../../binaries/data/mods/official/entities'; -my @xml; -find({wanted=>sub{ - push @xml, $_ if /\.xml$/; -}, no_chdir=>1}, $dir); -s~\Q$dir/~~ for @xml; - -my %nodes; - -for my $f (@xml) { - - $f =~ m~(?:.*/|^)(.*)\.xml~ or die "invalid filename $f"; - my $name = $1; - - open I, "$dir/$f" or die "error opening $dir/$f: $!"; - my $data = do { local $/; }; - close I; - - my $parent; - $parent = $1 if $data =~ /Parent="(.*?)"/; - - my ($upgrade, $rank); - $upgrade = $1 if $data =~ /\s*(.*?)\s*\s*(.*?)\s*1, parent=>$parent, upgrade=>[$upgrade, $rank], actor=>$actor }; -} - -open O, ">", "entities.dot" or die $!; - -print O < "$_";\n} if defined $nodes{$_}{parent}; -} - -print O "\n /* upgrade edges */\n"; -print O " edge [color=red fontcolor=red]\n"; -for (sort keys %nodes) { - if (defined $nodes{$_}{upgrade}[0]) { - print O qq{ "$_" -> "$nodes{$_}{upgrade}[0]"}; - print O qq{ [label="from rank $nodes{$_}{upgrade}[1]"]} if defined $nodes{$_}{upgrade}[1]; - print O qq{;\n}; - } -} -print O "}\n"; -close O; - +use strict; +use warnings; + +use File::Find; + +my $dir = '../../../binaries/data/mods/official/entities'; +my @xml; +find({wanted=>sub{ + push @xml, $_ if /\.xml$/; +}, no_chdir=>1}, $dir); +s~\Q$dir/~~ for @xml; + +my %nodes; + +for my $f (@xml) { + + $f =~ m~(?:.*/|^)(.*)\.xml~ or die "invalid filename $f"; + my $name = $1; + + open I, "$dir/$f" or die "error opening $dir/$f: $!"; + my $data = do { local $/; }; + close I; + + my $parent; + $parent = $1 if $data =~ /Parent="(.*?)"/; + + my ($upgrade, $rank); + $upgrade = $1 if $data =~ /\s*(.*?)\s*\s*(.*?)\s*1, parent=>$parent, upgrade=>[$upgrade, $rank], actor=>$actor }; +} + +open O, ">", "entities.dot" or die $!; + +print O < "$_";\n} if defined $nodes{$_}{parent}; +} + +print O "\n /* upgrade edges */\n"; +print O " edge [color=red fontcolor=red]\n"; +for (sort keys %nodes) { + if (defined $nodes{$_}{upgrade}[0]) { + print O qq{ "$_" -> "$nodes{$_}{upgrade}[0]"}; + print O qq{ [label="from rank $nodes{$_}{upgrade}[1]"]} if defined $nodes{$_}{upgrade}[1]; + print O qq{;\n}; + } +} +print O "}\n"; +close O; + system("dot.exe", "-Tpng", "entities.dot", "-o", "entities.png"); \ No newline at end of file diff --git a/source/tools/entity/checkrefs.pl b/source/tools/entity/checkrefs.pl index c477598ce6..aaa482d62b 100755 --- a/source/tools/entity/checkrefs.pl +++ b/source/tools/entity/checkrefs.pl @@ -1,581 +1,581 @@ -use strict; -use warnings; -use Data::Dumper; -use File::Find; -use XML::Simple; -use JSON; - -use Entity; - -use constant CHECK_MAPS_XML => 0; -use constant ROOT_ACTORS => 1; - -my @files; -my @roots; -my @deps; - -my $vfsroot = '../../../binaries/data/mods'; - -sub vfs_to_physical -{ - my ($vfspath) = @_; - my $fn = "$vfsroot/public/$vfspath"; - if (not -e $fn) - { - $fn = "$vfsroot/mod/$vfspath"; - } - return $fn; -} - -sub vfs_to_relative_to_mods -{ - my ($vfspath) = @_; - my $fn = "public/$vfspath"; - return $fn; -} - -sub find_files -{ - my ($vfspath, $extn) = @_; - my @files; - my $find_process = sub { - return $File::Find::prune = 1 if $_ eq '.svn'; - my $n = $File::Find::name; - return if /~$/; - return unless -f $_; - return unless /\.($extn)$/; - $n =~ s~\Q$vfsroot\E/(public|mod)/~~; - push @files, $n; - }; - find({ wanted => $find_process }, "$vfsroot/public/$vfspath"); - find({ wanted => $find_process }, "$vfsroot/mod/$vfspath") if -d "$vfsroot/mod/$vfspath"; - - return @files; -} - -sub parse_json_file -{ - my ($vfspath) = @_; - open my $fh, vfs_to_physical($vfspath) or die "Failed to open '$vfspath': $!"; - # decode_json expects a UTF-8 string and doesn't handle BOMs, so we strip those - # (see http://trac.wildfiregames.com/ticket/1556) - return decode_json(do { local $/; my $file = <$fh>; $file =~ s/^\xEF\xBB\xBF//; $file }); -} - -sub add_entities -{ - print "Loading entities...\n"; - - my @entfiles = find_files('simulation/templates', 'xml'); - s~^simulation/templates/(.*)\.xml$~$1~ for @entfiles; - - for my $f (sort @entfiles) - { - my $path = "simulation/templates/$f.xml"; - push @files, $path; - my $ent = Entity::load_inherited($f); - - push @deps, [ $path, "simulation/templates/" . $ent->{Entity}{'@parent'}{' content'} . ".xml" ] if $ent->{Entity}{'@parent'}; - - if ($f !~ /^template_/) - { - push @roots, $path; - if ($ent->{Entity}{VisualActor}) - { - push @deps, [ $path, "art/actors/" . $ent->{Entity}{VisualActor}{Actor}{' content'} ] if $ent->{Entity}{VisualActor}{Actor}; - push @deps, [ $path, "art/actors/" . $ent->{Entity}{VisualActor}{FoundationActor}{' content'} ] if $ent->{Entity}{VisualActor}{FoundationActor}; - } - - if ($ent->{Entity}{Sound}) - { - my $gender = $ent->{Entity}{Identity}{Gender}{' content'} || "male"; - my $lang = $ent->{Entity}{Identity}{Lang}{' content'} || "greek"; - - for (grep ref($_), values %{$ent->{Entity}{Sound}{SoundGroups}}) - { - # see simulation/components/Sound.js and Identity.js for explanation - my $soundPath = $_->{' content'}; - $soundPath =~ s/{gender}/$gender/g; - $soundPath =~ s/{lang}/$lang/g; - push @deps, [ $path, "audio/" . $soundPath ]; - } - } - - if ($ent->{Entity}{Identity}) - { - push @deps, [ $path, "art/textures/ui/session/portraits/" . $ent->{Entity}{Identity}{Icon}{' content'} ] if $ent->{Entity}{Identity}{Icon}; - } - } - } -} - -sub add_actors -{ - print "Loading actors...\n"; - - my @actorfiles = find_files('art/actors', 'xml'); - for my $f (sort @actorfiles) - { - push @files, $f; - - push @roots, $f if ROOT_ACTORS; - - my $actor = XMLin(vfs_to_physical($f), ForceArray => [qw(group variant texture prop animation)], KeyAttr => []) or die "Failed to parse '$f': $!"; - - for my $group (@{$actor->{group}}) - { - for my $variant (@{$group->{variant}}) - { - push @deps, [ $f, "art/meshes/$variant->{mesh}" ] if $variant->{mesh}; - push @deps, [ $f, "art/particles/$variant->{particles}{file}" ] if $variant->{particles}{file}; - for my $tex (@{$variant->{textures}{texture}}) - { - push @deps, [ $f, "art/textures/skins/$tex->{file}" ] if $tex->{file}; - } - for my $prop (@{$variant->{props}{prop}}) - { - push @deps, [ $f, "art/actors/$prop->{actor}" ] if $prop->{actor}; - } - for my $anim (@{$variant->{animations}{animation}}) - { - push @deps, [ $f, "art/animation/$anim->{file}" ] if $anim->{file}; - } - } - } - - push @deps, [ $f, "art/materials/$actor->{material}" ] if $actor->{material}; - } -} - -sub add_art -{ - print "Loading art files...\n"; - push @files, find_files('art/textures/particles', 'dds|png|jpg|tga'); - push @files, find_files('art/textures/terrain', 'dds|png|jpg|tga'); - push @files, find_files('art/textures/skins', 'dds|png|jpg|tga'); - push @files, find_files('art/meshes', 'pmd|dae'); - push @files, find_files('art/animation', 'psa|dae'); -} - -sub add_materials -{ - print "Loading materials...\n"; - my @materialfiles = find_files('art/materials', 'xml'); - for my $f (sort @materialfiles) - { - push @files, $f; - - my $material = XMLin(vfs_to_physical($f), ForceArray => [qw(alternative)], KeyAttr => []); - for my $alternative (@{$material->{alternative}}) - { - push @deps, [ $f, "art/materials/$alternative->{material}" ] if $alternative->{material}; - } - } -} - -sub add_particles -{ - print "Loading particles...\n"; - my @particlefiles = find_files('art/particles', 'xml'); - for my $f (sort @particlefiles) - { - push @files, $f; - - my $particle = XMLin(vfs_to_physical($f)); - push @deps, [ $f, "$particle->{texture}" ] if $particle->{texture}; - } -} - -sub add_maps_xml -{ - print "Loading maps XML...\n"; - my @mapfiles = find_files('maps/scenarios', 'xml'); - push @mapfiles, find_files('maps/skirmishes', 'xml'); - for my $f (sort @mapfiles) - { - print " $f\n"; - - push @files, $f; - - push @roots, $f; - - my $map = XMLin(vfs_to_physical($f), ForceArray => [qw(Entity)], KeyAttr => []) or die "Failed to parse '$f': $!"; - - my %used; - for my $entity (@{$map->{Entities}{Entity}}) - { - $used{$entity->{Template}} = 1; - } - - for my $template (keys %used) - { - if ($template =~ /^actor\|(.*)$/) - { - # Handle special 'actor|' case - push @deps, [ $f, "art/actors/$1" ]; - } - else - { - if ($template =~ /^resource\|(.*)$/) - { - # Handle special 'resource|' case - $template = $1; - } - push @deps, [ $f, "simulation/templates/$template.xml" ]; - } - } - - # Map previews - my $settings = decode_json($map->{ScriptSettings}); - push @deps, [ $f, "art/textures/ui/session/icons/mappreview/" . $settings->{Preview} ] if $settings->{Preview}; - } -} - -sub add_maps_pmp -{ - print "Loading maps PMP...\n"; - - # Need to generate terrain texture filename=>path lookup first - my %terrains; - for my $f (find_files('art/terrains', 'xml')) - { - $f =~ /([^\/]+)\.xml/ or die; - - # ignore terrains.xml - if ($f !~ /terrains.xml$/) - { - warn "Duplicate terrain name '$1' (from '$terrains{$1}' and '$f')\n" if $terrains{$1}; - $terrains{$1} = $f; - } - } - - my @mapfiles = find_files('maps/scenarios', 'pmp'); - push @mapfiles, find_files('maps/skirmishes', 'pmp'); - for my $f (sort @mapfiles) - { - push @files, $f; - - push @roots, $f; - - open my $fh, vfs_to_physical($f) or die "Failed to open '$f': $!"; - binmode $fh; - - my $buf; - - read $fh, $buf, 4; - die "Invalid PMP header ($buf) in '$f'" unless $buf eq "PSMP"; - - read $fh, $buf, 4; - my $version = unpack 'V', $buf; - die "Invalid PMP version ($version) in '$f'" unless $version == 6; - - read $fh, $buf, 4; - my $datasize = unpack 'V', $buf; - - read $fh, $buf, 4; - my $mapsize = unpack 'V', $buf; - - seek $fh, 2 * ($mapsize*16+1)*($mapsize*16+1), 1; # heightmap - - read $fh, $buf, 4; - my $numtexs = unpack 'V', $buf; - - for (0..$numtexs-1) - { - read $fh, $buf, 4; - my $len = unpack 'V', $buf; - my $str; - read $fh, $str, $len; - - push @deps, [ $f, $terrains{$str} || "art/terrains/(unknown)/$str" ]; - } - - # ignore patches data - } -} - -sub add_soundgroups -{ - print "Loading sound groups...\n"; - my @soundfiles = find_files('audio', 'xml'); - for my $f (sort @soundfiles) - { - push @files, $f; - - my $sound = XMLin(vfs_to_physical($f), ForceArray => [qw(Sound)], KeyAttr => []) or die "Failed to parse '$f': $!"; - - my $path = $sound->{Path}; - $path =~ s/\/$//; # strip optional trailing slash - - for (@{$sound->{Sound}}) - { - push @deps, [$f, "$path/$_" ]; - } - } -} - -sub add_audio -{ - print "Loading audio files...\n"; - push @files, find_files('audio', 'ogg'); -} - -sub add_gui_xml -{ - print "Loading GUI XML...\n"; - my @guifiles = find_files('gui', 'xml'); - for my $f (sort @guifiles) - { - push @files, $f; - - if ($f =~ /^gui\/page_/) - { - push @roots, $f; - my $xml = XMLin(vfs_to_physical($f), ForceArray => [qw(include)], KeyAttr => []) or die "Failed to parse '$f': $!"; - - for my $include (@{$xml->{include}}) - { - # If including an entire directory, find all the *.xml files - if ($include =~ /\/$/) - { - push @deps, [ $f, $_ ] for find_files("gui/$include", 'xml'); - } - else - { - push @deps, [ $f, "gui/$include" ]; - } - } - } - else - { - my $xml = XMLin(vfs_to_physical($f), ForceArray => [qw(object script action sprite image)], KeyAttr => [], KeepRoot => 1) or die "Failed to parse '$f': $!"; - my $name = (keys %$xml)[0]; - if ($name eq 'objects' or $name eq 'object') - { - for (grep ref $_ , @{$xml->{objects}{script}}) - { - push @deps, [ $f, $_->{file} ] if $_->{file}; - if ($_->{directory}) - { - # If including an entire directory, find all the *.js files - push @deps, [ $f, $_ ] for find_files($_->{directory}, 'js') - } - } - my $add_objects; - $add_objects = sub - { - my ($parent) = @_; - for my $obj (@{$parent->{object}}) - { - # TODO: look at sprites, styles, etc - $add_objects->($obj); - } - }; - $add_objects->($xml->{objects}); - } - elsif ($name eq 'setup') - { - # TODO: look at sprites, styles, etc - } - elsif ($name eq 'styles') - { - # TODO: look at sprites, styles, etc - } - elsif ($name eq 'sprites') - { - for my $sprite (@{$xml->{sprites}{sprite}}) - { - for my $image (@{$sprite->{image}}) - { - push @deps, [ $f, "art/textures/ui/$image->{texture}" ] if $image->{texture}; - } - } - } - else - { - print "Unexpected GUI XML root element '$name':\n" . Dumper $xml; - exit; - } - } - } -} - -sub add_gui_data -{ - print "Loading GUI data...\n"; - push @files, find_files('gui', 'js'); - push @files, find_files('art/textures/ui', 'dds|png|jpg|tga'); -} - -sub add_civs -{ - print "Loading civs...\n"; - - my @civfiles = find_files('simulation/data/civs', 'json'); - for my $f (sort @civfiles) - { - push @files, $f; - - push @roots, $f; - - my $civ = parse_json_file($f); - - push @deps, [ $f, "art/textures/ui/" . $civ->{Emblem} ]; - - push @deps, [ $f, "audio/music/" . $_->{File} ] for @{$civ->{Music}}; - } -} - -sub add_rms -{ - print "Loading random maps...\n"; - - push @files, find_files('maps/random', 'js'); - my @rmsdefs = find_files('maps/random', 'json'); - - for my $f (sort @rmsdefs) - { - push @files, $f; - - push @roots, $f; - - my $rms = parse_json_file($f); - - push @deps, [ $f, "maps/random/" . $rms->{settings}{Script} ]; - - # Map previews - push @deps, [ $f, "art/textures/ui/session/icons/mappreview/" . $rms->{settings}{Preview} ] if $rms->{settings}{Preview}; - } -} - -sub add_techs -{ - print "Loading techs...\n"; - - my @techfiles = find_files('simulation/data/technologies', 'json'); - for my $f (sort @techfiles) - { - push @files, $f; - push @roots, $f; - - my $tech = parse_json_file($f); - - push @deps, [ $f, "art/textures/ui/session/portraits/technologies/" . $tech->{icon} ] if $tech->{icon}; - push @deps, [ $f, "simulation/data/technologies/" . $tech->{supersedes} . ".json" ] if $tech->{supersedes}; - } -} - -sub add_terrains -{ - print "Loading terrains...\n"; - - my @terrains = find_files('art/terrains', 'xml'); - for my $f (sort @terrains) - { - # ignore terrains.xml - if ($f !~ /terrains.xml$/) - { - push @files, $f; - - my $terrain = XMLin(vfs_to_physical($f), ForceArray => [qw(texture)], KeyAttr => []) or die "Failed to parse '$f': $!"; - - for my $texture (@{$terrain->{textures}{texture}}) - { - push @deps, [ $f, "art/textures/terrain/$texture->{file}" ] if $texture->{file}; - } - push @deps, [ $f, "art/materials/$terrain->{material}" ] if $terrain->{material}; - } - } -} - - -sub check_deps -{ - my %files; - @files{@files} = (); - - my %lcfiles; - @lcfiles{map lc($_), @files} = @files; - - my %revdeps; - for my $d (@deps) - { - push @{$revdeps{$d->[1]}}, $d->[0]; - } - - for my $f (sort keys %revdeps) - { - next if exists $files{$f}; - warn "Missing file '$f' referenced by: " . (join ', ', map "'$_'", map vfs_to_relative_to_mods($_), sort @{$revdeps{$f}}) . "\n"; - - if (exists $lcfiles{lc $f}) - { - warn "### Case-insensitive match (found '$lcfiles{lc $f}')\n"; - } - } -} - -sub check_unused -{ - my %reachable; - @reachable{@roots} = (); - - my %deps; - for my $d (@deps) - { - push @{$deps{$d->[0]}}, $d->[1]; - } - - while (1) - { - my @newreachable; - for my $r (keys %reachable) - { - push @newreachable, grep { not exists $reachable{$_} } @{$deps{$r}}; - } - last if @newreachable == 0; - @reachable{@newreachable} = (); - } - - for my $f (sort @files) - { - next if exists $reachable{$f}; - warn "Unused file '" . vfs_to_relative_to_mods($f) . "'\n"; - } -} - - -add_maps_xml() if CHECK_MAPS_XML; - -add_maps_pmp(); - -add_entities(); - -add_actors(); - -add_art(); - -add_materials(); - -add_particles(); - -add_soundgroups(); -add_audio(); - -add_gui_xml(); -add_gui_data(); - -add_civs(); - -add_rms(); - -add_techs(); - -add_terrains(); - -# TODO: add non-skin textures, and all the references to them - -print "\n"; -check_deps(); -print "\n"; -check_unused(); +use strict; +use warnings; +use Data::Dumper; +use File::Find; +use XML::Simple; +use JSON; + +use Entity; + +use constant CHECK_MAPS_XML => 0; +use constant ROOT_ACTORS => 1; + +my @files; +my @roots; +my @deps; + +my $vfsroot = '../../../binaries/data/mods'; + +sub vfs_to_physical +{ + my ($vfspath) = @_; + my $fn = "$vfsroot/public/$vfspath"; + if (not -e $fn) + { + $fn = "$vfsroot/mod/$vfspath"; + } + return $fn; +} + +sub vfs_to_relative_to_mods +{ + my ($vfspath) = @_; + my $fn = "public/$vfspath"; + return $fn; +} + +sub find_files +{ + my ($vfspath, $extn) = @_; + my @files; + my $find_process = sub { + return $File::Find::prune = 1 if $_ eq '.svn'; + my $n = $File::Find::name; + return if /~$/; + return unless -f $_; + return unless /\.($extn)$/; + $n =~ s~\Q$vfsroot\E/(public|mod)/~~; + push @files, $n; + }; + find({ wanted => $find_process }, "$vfsroot/public/$vfspath"); + find({ wanted => $find_process }, "$vfsroot/mod/$vfspath") if -d "$vfsroot/mod/$vfspath"; + + return @files; +} + +sub parse_json_file +{ + my ($vfspath) = @_; + open my $fh, vfs_to_physical($vfspath) or die "Failed to open '$vfspath': $!"; + # decode_json expects a UTF-8 string and doesn't handle BOMs, so we strip those + # (see http://trac.wildfiregames.com/ticket/1556) + return decode_json(do { local $/; my $file = <$fh>; $file =~ s/^\xEF\xBB\xBF//; $file }); +} + +sub add_entities +{ + print "Loading entities...\n"; + + my @entfiles = find_files('simulation/templates', 'xml'); + s~^simulation/templates/(.*)\.xml$~$1~ for @entfiles; + + for my $f (sort @entfiles) + { + my $path = "simulation/templates/$f.xml"; + push @files, $path; + my $ent = Entity::load_inherited($f); + + push @deps, [ $path, "simulation/templates/" . $ent->{Entity}{'@parent'}{' content'} . ".xml" ] if $ent->{Entity}{'@parent'}; + + if ($f !~ /^template_/) + { + push @roots, $path; + if ($ent->{Entity}{VisualActor}) + { + push @deps, [ $path, "art/actors/" . $ent->{Entity}{VisualActor}{Actor}{' content'} ] if $ent->{Entity}{VisualActor}{Actor}; + push @deps, [ $path, "art/actors/" . $ent->{Entity}{VisualActor}{FoundationActor}{' content'} ] if $ent->{Entity}{VisualActor}{FoundationActor}; + } + + if ($ent->{Entity}{Sound}) + { + my $gender = $ent->{Entity}{Identity}{Gender}{' content'} || "male"; + my $lang = $ent->{Entity}{Identity}{Lang}{' content'} || "greek"; + + for (grep ref($_), values %{$ent->{Entity}{Sound}{SoundGroups}}) + { + # see simulation/components/Sound.js and Identity.js for explanation + my $soundPath = $_->{' content'}; + $soundPath =~ s/{gender}/$gender/g; + $soundPath =~ s/{lang}/$lang/g; + push @deps, [ $path, "audio/" . $soundPath ]; + } + } + + if ($ent->{Entity}{Identity}) + { + push @deps, [ $path, "art/textures/ui/session/portraits/" . $ent->{Entity}{Identity}{Icon}{' content'} ] if $ent->{Entity}{Identity}{Icon}; + } + } + } +} + +sub add_actors +{ + print "Loading actors...\n"; + + my @actorfiles = find_files('art/actors', 'xml'); + for my $f (sort @actorfiles) + { + push @files, $f; + + push @roots, $f if ROOT_ACTORS; + + my $actor = XMLin(vfs_to_physical($f), ForceArray => [qw(group variant texture prop animation)], KeyAttr => []) or die "Failed to parse '$f': $!"; + + for my $group (@{$actor->{group}}) + { + for my $variant (@{$group->{variant}}) + { + push @deps, [ $f, "art/meshes/$variant->{mesh}" ] if $variant->{mesh}; + push @deps, [ $f, "art/particles/$variant->{particles}{file}" ] if $variant->{particles}{file}; + for my $tex (@{$variant->{textures}{texture}}) + { + push @deps, [ $f, "art/textures/skins/$tex->{file}" ] if $tex->{file}; + } + for my $prop (@{$variant->{props}{prop}}) + { + push @deps, [ $f, "art/actors/$prop->{actor}" ] if $prop->{actor}; + } + for my $anim (@{$variant->{animations}{animation}}) + { + push @deps, [ $f, "art/animation/$anim->{file}" ] if $anim->{file}; + } + } + } + + push @deps, [ $f, "art/materials/$actor->{material}" ] if $actor->{material}; + } +} + +sub add_art +{ + print "Loading art files...\n"; + push @files, find_files('art/textures/particles', 'dds|png|jpg|tga'); + push @files, find_files('art/textures/terrain', 'dds|png|jpg|tga'); + push @files, find_files('art/textures/skins', 'dds|png|jpg|tga'); + push @files, find_files('art/meshes', 'pmd|dae'); + push @files, find_files('art/animation', 'psa|dae'); +} + +sub add_materials +{ + print "Loading materials...\n"; + my @materialfiles = find_files('art/materials', 'xml'); + for my $f (sort @materialfiles) + { + push @files, $f; + + my $material = XMLin(vfs_to_physical($f), ForceArray => [qw(alternative)], KeyAttr => []); + for my $alternative (@{$material->{alternative}}) + { + push @deps, [ $f, "art/materials/$alternative->{material}" ] if $alternative->{material}; + } + } +} + +sub add_particles +{ + print "Loading particles...\n"; + my @particlefiles = find_files('art/particles', 'xml'); + for my $f (sort @particlefiles) + { + push @files, $f; + + my $particle = XMLin(vfs_to_physical($f)); + push @deps, [ $f, "$particle->{texture}" ] if $particle->{texture}; + } +} + +sub add_maps_xml +{ + print "Loading maps XML...\n"; + my @mapfiles = find_files('maps/scenarios', 'xml'); + push @mapfiles, find_files('maps/skirmishes', 'xml'); + for my $f (sort @mapfiles) + { + print " $f\n"; + + push @files, $f; + + push @roots, $f; + + my $map = XMLin(vfs_to_physical($f), ForceArray => [qw(Entity)], KeyAttr => []) or die "Failed to parse '$f': $!"; + + my %used; + for my $entity (@{$map->{Entities}{Entity}}) + { + $used{$entity->{Template}} = 1; + } + + for my $template (keys %used) + { + if ($template =~ /^actor\|(.*)$/) + { + # Handle special 'actor|' case + push @deps, [ $f, "art/actors/$1" ]; + } + else + { + if ($template =~ /^resource\|(.*)$/) + { + # Handle special 'resource|' case + $template = $1; + } + push @deps, [ $f, "simulation/templates/$template.xml" ]; + } + } + + # Map previews + my $settings = decode_json($map->{ScriptSettings}); + push @deps, [ $f, "art/textures/ui/session/icons/mappreview/" . $settings->{Preview} ] if $settings->{Preview}; + } +} + +sub add_maps_pmp +{ + print "Loading maps PMP...\n"; + + # Need to generate terrain texture filename=>path lookup first + my %terrains; + for my $f (find_files('art/terrains', 'xml')) + { + $f =~ /([^\/]+)\.xml/ or die; + + # ignore terrains.xml + if ($f !~ /terrains.xml$/) + { + warn "Duplicate terrain name '$1' (from '$terrains{$1}' and '$f')\n" if $terrains{$1}; + $terrains{$1} = $f; + } + } + + my @mapfiles = find_files('maps/scenarios', 'pmp'); + push @mapfiles, find_files('maps/skirmishes', 'pmp'); + for my $f (sort @mapfiles) + { + push @files, $f; + + push @roots, $f; + + open my $fh, vfs_to_physical($f) or die "Failed to open '$f': $!"; + binmode $fh; + + my $buf; + + read $fh, $buf, 4; + die "Invalid PMP header ($buf) in '$f'" unless $buf eq "PSMP"; + + read $fh, $buf, 4; + my $version = unpack 'V', $buf; + die "Invalid PMP version ($version) in '$f'" unless $version == 6; + + read $fh, $buf, 4; + my $datasize = unpack 'V', $buf; + + read $fh, $buf, 4; + my $mapsize = unpack 'V', $buf; + + seek $fh, 2 * ($mapsize*16+1)*($mapsize*16+1), 1; # heightmap + + read $fh, $buf, 4; + my $numtexs = unpack 'V', $buf; + + for (0..$numtexs-1) + { + read $fh, $buf, 4; + my $len = unpack 'V', $buf; + my $str; + read $fh, $str, $len; + + push @deps, [ $f, $terrains{$str} || "art/terrains/(unknown)/$str" ]; + } + + # ignore patches data + } +} + +sub add_soundgroups +{ + print "Loading sound groups...\n"; + my @soundfiles = find_files('audio', 'xml'); + for my $f (sort @soundfiles) + { + push @files, $f; + + my $sound = XMLin(vfs_to_physical($f), ForceArray => [qw(Sound)], KeyAttr => []) or die "Failed to parse '$f': $!"; + + my $path = $sound->{Path}; + $path =~ s/\/$//; # strip optional trailing slash + + for (@{$sound->{Sound}}) + { + push @deps, [$f, "$path/$_" ]; + } + } +} + +sub add_audio +{ + print "Loading audio files...\n"; + push @files, find_files('audio', 'ogg'); +} + +sub add_gui_xml +{ + print "Loading GUI XML...\n"; + my @guifiles = find_files('gui', 'xml'); + for my $f (sort @guifiles) + { + push @files, $f; + + if ($f =~ /^gui\/page_/) + { + push @roots, $f; + my $xml = XMLin(vfs_to_physical($f), ForceArray => [qw(include)], KeyAttr => []) or die "Failed to parse '$f': $!"; + + for my $include (@{$xml->{include}}) + { + # If including an entire directory, find all the *.xml files + if ($include =~ /\/$/) + { + push @deps, [ $f, $_ ] for find_files("gui/$include", 'xml'); + } + else + { + push @deps, [ $f, "gui/$include" ]; + } + } + } + else + { + my $xml = XMLin(vfs_to_physical($f), ForceArray => [qw(object script action sprite image)], KeyAttr => [], KeepRoot => 1) or die "Failed to parse '$f': $!"; + my $name = (keys %$xml)[0]; + if ($name eq 'objects' or $name eq 'object') + { + for (grep ref $_ , @{$xml->{objects}{script}}) + { + push @deps, [ $f, $_->{file} ] if $_->{file}; + if ($_->{directory}) + { + # If including an entire directory, find all the *.js files + push @deps, [ $f, $_ ] for find_files($_->{directory}, 'js') + } + } + my $add_objects; + $add_objects = sub + { + my ($parent) = @_; + for my $obj (@{$parent->{object}}) + { + # TODO: look at sprites, styles, etc + $add_objects->($obj); + } + }; + $add_objects->($xml->{objects}); + } + elsif ($name eq 'setup') + { + # TODO: look at sprites, styles, etc + } + elsif ($name eq 'styles') + { + # TODO: look at sprites, styles, etc + } + elsif ($name eq 'sprites') + { + for my $sprite (@{$xml->{sprites}{sprite}}) + { + for my $image (@{$sprite->{image}}) + { + push @deps, [ $f, "art/textures/ui/$image->{texture}" ] if $image->{texture}; + } + } + } + else + { + print "Unexpected GUI XML root element '$name':\n" . Dumper $xml; + exit; + } + } + } +} + +sub add_gui_data +{ + print "Loading GUI data...\n"; + push @files, find_files('gui', 'js'); + push @files, find_files('art/textures/ui', 'dds|png|jpg|tga'); +} + +sub add_civs +{ + print "Loading civs...\n"; + + my @civfiles = find_files('simulation/data/civs', 'json'); + for my $f (sort @civfiles) + { + push @files, $f; + + push @roots, $f; + + my $civ = parse_json_file($f); + + push @deps, [ $f, "art/textures/ui/" . $civ->{Emblem} ]; + + push @deps, [ $f, "audio/music/" . $_->{File} ] for @{$civ->{Music}}; + } +} + +sub add_rms +{ + print "Loading random maps...\n"; + + push @files, find_files('maps/random', 'js'); + my @rmsdefs = find_files('maps/random', 'json'); + + for my $f (sort @rmsdefs) + { + push @files, $f; + + push @roots, $f; + + my $rms = parse_json_file($f); + + push @deps, [ $f, "maps/random/" . $rms->{settings}{Script} ]; + + # Map previews + push @deps, [ $f, "art/textures/ui/session/icons/mappreview/" . $rms->{settings}{Preview} ] if $rms->{settings}{Preview}; + } +} + +sub add_techs +{ + print "Loading techs...\n"; + + my @techfiles = find_files('simulation/data/technologies', 'json'); + for my $f (sort @techfiles) + { + push @files, $f; + push @roots, $f; + + my $tech = parse_json_file($f); + + push @deps, [ $f, "art/textures/ui/session/portraits/technologies/" . $tech->{icon} ] if $tech->{icon}; + push @deps, [ $f, "simulation/data/technologies/" . $tech->{supersedes} . ".json" ] if $tech->{supersedes}; + } +} + +sub add_terrains +{ + print "Loading terrains...\n"; + + my @terrains = find_files('art/terrains', 'xml'); + for my $f (sort @terrains) + { + # ignore terrains.xml + if ($f !~ /terrains.xml$/) + { + push @files, $f; + + my $terrain = XMLin(vfs_to_physical($f), ForceArray => [qw(texture)], KeyAttr => []) or die "Failed to parse '$f': $!"; + + for my $texture (@{$terrain->{textures}{texture}}) + { + push @deps, [ $f, "art/textures/terrain/$texture->{file}" ] if $texture->{file}; + } + push @deps, [ $f, "art/materials/$terrain->{material}" ] if $terrain->{material}; + } + } +} + + +sub check_deps +{ + my %files; + @files{@files} = (); + + my %lcfiles; + @lcfiles{map lc($_), @files} = @files; + + my %revdeps; + for my $d (@deps) + { + push @{$revdeps{$d->[1]}}, $d->[0]; + } + + for my $f (sort keys %revdeps) + { + next if exists $files{$f}; + warn "Missing file '$f' referenced by: " . (join ', ', map "'$_'", map vfs_to_relative_to_mods($_), sort @{$revdeps{$f}}) . "\n"; + + if (exists $lcfiles{lc $f}) + { + warn "### Case-insensitive match (found '$lcfiles{lc $f}')\n"; + } + } +} + +sub check_unused +{ + my %reachable; + @reachable{@roots} = (); + + my %deps; + for my $d (@deps) + { + push @{$deps{$d->[0]}}, $d->[1]; + } + + while (1) + { + my @newreachable; + for my $r (keys %reachable) + { + push @newreachable, grep { not exists $reachable{$_} } @{$deps{$r}}; + } + last if @newreachable == 0; + @reachable{@newreachable} = (); + } + + for my $f (sort @files) + { + next if exists $reachable{$f}; + warn "Unused file '" . vfs_to_relative_to_mods($f) . "'\n"; + } +} + + +add_maps_xml() if CHECK_MAPS_XML; + +add_maps_pmp(); + +add_entities(); + +add_actors(); + +add_art(); + +add_materials(); + +add_particles(); + +add_soundgroups(); +add_audio(); + +add_gui_xml(); +add_gui_data(); + +add_civs(); + +add_rms(); + +add_techs(); + +add_terrains(); + +# TODO: add non-skin textures, and all the references to them + +print "\n"; +check_deps(); +print "\n"; +check_unused(); diff --git a/source/tools/fontbuilder2/README.txt b/source/tools/fontbuilder2/README.txt index 0c84ec93d9..1b395c1729 100644 --- a/source/tools/fontbuilder2/README.txt +++ b/source/tools/fontbuilder2/README.txt @@ -1,72 +1,72 @@ -0 A.D. Font Builder -==================== - -The Font Builder generates pre-rendered font glyphs for use in the game engine. Its output for each font consists -of an 8-bit greyscale PNG image and a descriptor .fnt file that describes and locates each individual glyph in the image -(see fileformat.txt for details). - -See the wiki page for more information: - - http://trac.wildfiregames.com/wiki/Font_Builder2 - -Prerequisites -------------- - -The main prerequisite for the fontbuilder is the Cairo imaging library and its Python bindings, PyCairo. On most -Linux distributions, this should be merely a matter of installing a package (e.g. 'python-cairo' for Debian/Ubuntu), -but on Windows it's more involved. - -We'll demonstrate the process for Windows 32-bit first. Grab a Win32 binary for PyCairo from - - http://ftp.gnome.org/pub/GNOME/binaries/win32/pycairo/1.8/ - -and install it using the installer. There are installers available for Python versions 2.6 and 2.7. The installer -extracts the necessary files into Lib\site-packages\cairo within your Python installation directory. - -Next is Cairo itself, and some dependencies which are required for Cairo to work. Head to - - http://ftp.gnome.org/pub/gnome/binaries/win32/dependencies/ - -and get the following binaries. Listed next to each are their version numbers at the time of writing; these may vary -over time, so be adaptive! - - - Cairo (cairo_1.8.10-3_win32.zip) - - Fontconfig (fontconfig_2.8.0-2_win32.zip) - - Freetype (freetype_2.4.4-1_win32.zip) - - Expat (expat_2.0.1-1_win32.zip) - - libpng (libpng_1.4.3-1_win32.zip) - - zlib (zlib_1.2.5-2_win32.zip). - -Each ZIP file will contain a bin subfolder with a DLL file in it. Put the following DLLs in Lib\site-packages\cairo -within your Python installation: - - libcairo-2.dll (from cairo_1.8.10-3_win32.zip) - libfontconfig-1.dll (from fontconfig_2.8.0-2_win32.zip) - freetype6.dll (from freetype_2.4.4-1_win32.zip) - libexpat-1.dll (from expat_2.0.1-1_win32.zip) - libpng14-14.dll (from libpng_1.4.3-1_win32.zip) - zlib1.dll (from zlib_1.2.5-2_win32.zip). - -You should be all set now. To test whether PyCairo installed successfully, try running the following command on a -command line: - -python -c "import cairo" - -If it doesn't complain, then it's installed successfully. - -On Windows 64-bit, the process is similar, but no pre-built PyCairo executable appears to be available from Gnome at -the time of writing. Instead, you can install PyGTK+ for 64-bit Windows, which includes Cairo, PyCairo, and the -same set of dependencies. See this page for details: - - http://www.pygtk.org/downloads.html - -Running -------- - -Running the font-builder is fairly straight-forward; there are no configuration options. One caveat is that you must -run it from its own directory as the current directory. - - python fontbuilder.py - -This will generate the output .png and .fnt files straight into the binaries/data/mods/public/fonts directory, ready -for in-game use. +0 A.D. Font Builder +==================== + +The Font Builder generates pre-rendered font glyphs for use in the game engine. Its output for each font consists +of an 8-bit greyscale PNG image and a descriptor .fnt file that describes and locates each individual glyph in the image +(see fileformat.txt for details). + +See the wiki page for more information: + + http://trac.wildfiregames.com/wiki/Font_Builder2 + +Prerequisites +------------- + +The main prerequisite for the fontbuilder is the Cairo imaging library and its Python bindings, PyCairo. On most +Linux distributions, this should be merely a matter of installing a package (e.g. 'python-cairo' for Debian/Ubuntu), +but on Windows it's more involved. + +We'll demonstrate the process for Windows 32-bit first. Grab a Win32 binary for PyCairo from + + http://ftp.gnome.org/pub/GNOME/binaries/win32/pycairo/1.8/ + +and install it using the installer. There are installers available for Python versions 2.6 and 2.7. The installer +extracts the necessary files into Lib\site-packages\cairo within your Python installation directory. + +Next is Cairo itself, and some dependencies which are required for Cairo to work. Head to + + http://ftp.gnome.org/pub/gnome/binaries/win32/dependencies/ + +and get the following binaries. Listed next to each are their version numbers at the time of writing; these may vary +over time, so be adaptive! + + - Cairo (cairo_1.8.10-3_win32.zip) + - Fontconfig (fontconfig_2.8.0-2_win32.zip) + - Freetype (freetype_2.4.4-1_win32.zip) + - Expat (expat_2.0.1-1_win32.zip) + - libpng (libpng_1.4.3-1_win32.zip) + - zlib (zlib_1.2.5-2_win32.zip). + +Each ZIP file will contain a bin subfolder with a DLL file in it. Put the following DLLs in Lib\site-packages\cairo +within your Python installation: + + libcairo-2.dll (from cairo_1.8.10-3_win32.zip) + libfontconfig-1.dll (from fontconfig_2.8.0-2_win32.zip) + freetype6.dll (from freetype_2.4.4-1_win32.zip) + libexpat-1.dll (from expat_2.0.1-1_win32.zip) + libpng14-14.dll (from libpng_1.4.3-1_win32.zip) + zlib1.dll (from zlib_1.2.5-2_win32.zip). + +You should be all set now. To test whether PyCairo installed successfully, try running the following command on a +command line: + +python -c "import cairo" + +If it doesn't complain, then it's installed successfully. + +On Windows 64-bit, the process is similar, but no pre-built PyCairo executable appears to be available from Gnome at +the time of writing. Instead, you can install PyGTK+ for 64-bit Windows, which includes Cairo, PyCairo, and the +same set of dependencies. See this page for details: + + http://www.pygtk.org/downloads.html + +Running +------- + +Running the font-builder is fairly straight-forward; there are no configuration options. One caveat is that you must +run it from its own directory as the current directory. + + python fontbuilder.py + +This will generate the output .png and .fnt files straight into the binaries/data/mods/public/fonts directory, ready +for in-game use. diff --git a/source/tools/fontbuilder2/charset.txt b/source/tools/fontbuilder2/charset.txt index 4e8504d165..0fad12969d 100644 --- a/source/tools/fontbuilder2/charset.txt +++ b/source/tools/fontbuilder2/charset.txt @@ -1,1358 +1,1358 @@ -# Basic Latin -0020: SPACE -0021: ! EXCLAMATION MARK -0022: " QUOTATION MARK -0023: # NUMBER SIGN -0024: $ DOLLAR SIGN -0025: % PERCENT SIGN -0026: & AMPERSAND -0027: ' APOSTROPHE -0028: ( LEFT PARENTHESIS -0029: ) RIGHT PARENTHESIS -002A: * ASTERISK -002B: + PLUS SIGN -002C: , COMMA -002D: - HYPHEN-MINUS -002E: . FULL STOP -002F: / SOLIDUS -0030: 0 DIGIT ZERO -0031: 1 DIGIT ONE -0032: 2 DIGIT TWO -0033: 3 DIGIT THREE -0034: 4 DIGIT FOUR -0035: 5 DIGIT FIVE -0036: 6 DIGIT SIX -0037: 7 DIGIT SEVEN -0038: 8 DIGIT EIGHT -0039: 9 DIGIT NINE -003A: : COLON -003B: ; SEMICOLON -003C: < LESS-THAN SIGN -003D: = EQUALS SIGN -003E: > GREATER-THAN SIGN -003F: ? QUESTION MARK -0040: @ COMMERCIAL AT -0041: A LATIN CAPITAL LETTER A -0042: B LATIN CAPITAL LETTER B -0043: C LATIN CAPITAL LETTER C -0044: D LATIN CAPITAL LETTER D -0045: E LATIN CAPITAL LETTER E -0046: F LATIN CAPITAL LETTER F -0047: G LATIN CAPITAL LETTER G -0048: H LATIN CAPITAL LETTER H -0049: I LATIN CAPITAL LETTER I -004A: J LATIN CAPITAL LETTER J -004B: K LATIN CAPITAL LETTER K -004C: L LATIN CAPITAL LETTER L -004D: M LATIN CAPITAL LETTER M -004E: N LATIN CAPITAL LETTER N -004F: O LATIN CAPITAL LETTER O -0050: P LATIN CAPITAL LETTER P -0051: Q LATIN CAPITAL LETTER Q -0052: R LATIN CAPITAL LETTER R -0053: S LATIN CAPITAL LETTER S -0054: T LATIN CAPITAL LETTER T -0055: U LATIN CAPITAL LETTER U -0056: V LATIN CAPITAL LETTER V -0057: W LATIN CAPITAL LETTER W -0058: X LATIN CAPITAL LETTER X -0059: Y LATIN CAPITAL LETTER Y -005A: Z LATIN CAPITAL LETTER Z -005B: [ LEFT SQUARE BRACKET -005C: \ REVERSE SOLIDUS -005D: ] RIGHT SQUARE BRACKET -005E: ^ CIRCUMFLEX ACCENT -005F: _ LOW LINE -0060: ` GRAVE ACCENT -0061: a LATIN SMALL LETTER A -0062: b LATIN SMALL LETTER B -0063: c LATIN SMALL LETTER C -0064: d LATIN SMALL LETTER D -0065: e LATIN SMALL LETTER E -0066: f LATIN SMALL LETTER F -0067: g LATIN SMALL LETTER G -0068: h LATIN SMALL LETTER H -0069: i LATIN SMALL LETTER I -006A: j LATIN SMALL LETTER J -006B: k LATIN SMALL LETTER K -006C: l LATIN SMALL LETTER L -006D: m LATIN SMALL LETTER M -006E: n LATIN SMALL LETTER N -006F: o LATIN SMALL LETTER O -0070: p LATIN SMALL LETTER P -0071: q LATIN SMALL LETTER Q -0072: r LATIN SMALL LETTER R -0073: s LATIN SMALL LETTER S -0074: t LATIN SMALL LETTER T -0075: u LATIN SMALL LETTER U -0076: v LATIN SMALL LETTER V -0077: w LATIN SMALL LETTER W -0078: x LATIN SMALL LETTER X -0079: y LATIN SMALL LETTER Y -007A: z LATIN SMALL LETTER Z -007B: { LEFT CURLY BRACKET -007C: | VERTICAL LINE -007D: } RIGHT CURLY BRACKET -007E: ~ TILDE - -# Latin-1 Supplement -00A0:   NO-BREAK SPACE -00A1: ¡ INVERTED EXCLAMATION MARK -00A2: ¢ CENT SIGN -00A3: £ POUND SIGN -00A4: ¤ CURRENCY SIGN -00A5: ¥ YEN SIGN -00A6: ¦ BROKEN BAR -00A7: § SECTION SIGN -00A8: ¨ DIAERESIS -00A9: © COPYRIGHT SIGN -00AA: ª FEMININE ORDINAL INDICATOR -00AB: « LEFT-POINTING DOUBLE ANGLE QUOTATION MARK -00AC: ¬ NOT SIGN -00AD: ­ SOFT HYPHEN -00AE: ® REGISTERED SIGN -00AF: ¯ MACRON -00B0: ° DEGREE SIGN -00B1: ± PLUS-MINUS SIGN -00B2: ² SUPERSCRIPT TWO -00B3: ³ SUPERSCRIPT THREE -00B4: ´ ACUTE ACCENT -00B5: µ MICRO SIGN -00B6: ¶ PILCROW SIGN -00B7: · MIDDLE DOT -00B8: ¸ CEDILLA -00B9: ¹ SUPERSCRIPT ONE -00BA: º MASCULINE ORDINAL INDICATOR -00BB: » RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK -00BC: ¼ VULGAR FRACTION ONE QUARTER -00BD: ½ VULGAR FRACTION ONE HALF -00BE: ¾ VULGAR FRACTION THREE QUARTERS -00BF: ¿ INVERTED QUESTION MARK -00C0: À LATIN CAPITAL LETTER A WITH GRAVE -00C1: Á LATIN CAPITAL LETTER A WITH ACUTE -00C2:  LATIN CAPITAL LETTER A WITH CIRCUMFLEX -00C3: à LATIN CAPITAL LETTER A WITH TILDE -00C4: Ä LATIN CAPITAL LETTER A WITH DIAERESIS -00C5: Å LATIN CAPITAL LETTER A WITH RING ABOVE -00C6: Æ LATIN CAPITAL LETTER AE -00C7: Ç LATIN CAPITAL LETTER C WITH CEDILLA -00C8: È LATIN CAPITAL LETTER E WITH GRAVE -00C9: É LATIN CAPITAL LETTER E WITH ACUTE -00CA: Ê LATIN CAPITAL LETTER E WITH CIRCUMFLEX -00CB: Ë LATIN CAPITAL LETTER E WITH DIAERESIS -00CC: Ì LATIN CAPITAL LETTER I WITH GRAVE -00CD: Í LATIN CAPITAL LETTER I WITH ACUTE -00CE: Î LATIN CAPITAL LETTER I WITH CIRCUMFLEX -00CF: Ï LATIN CAPITAL LETTER I WITH DIAERESIS -00D0: Ð LATIN CAPITAL LETTER ETH -00D1: Ñ LATIN CAPITAL LETTER N WITH TILDE -00D2: Ò LATIN CAPITAL LETTER O WITH GRAVE -00D3: Ó LATIN CAPITAL LETTER O WITH ACUTE -00D4: Ô LATIN CAPITAL LETTER O WITH CIRCUMFLEX -00D5: Õ LATIN CAPITAL LETTER O WITH TILDE -00D6: Ö LATIN CAPITAL LETTER O WITH DIAERESIS -00D7: × MULTIPLICATION SIGN -00D8: Ø LATIN CAPITAL LETTER O WITH STROKE -00D9: Ù LATIN CAPITAL LETTER U WITH GRAVE -00DA: Ú LATIN CAPITAL LETTER U WITH ACUTE -00DB: Û LATIN CAPITAL LETTER U WITH CIRCUMFLEX -00DC: Ü LATIN CAPITAL LETTER U WITH DIAERESIS -00DD: Ý LATIN CAPITAL LETTER Y WITH ACUTE -00DE: Þ LATIN CAPITAL LETTER THORN -00DF: ß LATIN SMALL LETTER SHARP S -00E0: à LATIN SMALL LETTER A WITH GRAVE -00E1: á LATIN SMALL LETTER A WITH ACUTE -00E2: â LATIN SMALL LETTER A WITH CIRCUMFLEX -00E3: ã LATIN SMALL LETTER A WITH TILDE -00E4: ä LATIN SMALL LETTER A WITH DIAERESIS -00E5: å LATIN SMALL LETTER A WITH RING ABOVE -00E6: æ LATIN SMALL LETTER AE -00E7: ç LATIN SMALL LETTER C WITH CEDILLA -00E8: è LATIN SMALL LETTER E WITH GRAVE -00E9: é LATIN SMALL LETTER E WITH ACUTE -00EA: ê LATIN SMALL LETTER E WITH CIRCUMFLEX -00EB: ë LATIN SMALL LETTER E WITH DIAERESIS -00EC: ì LATIN SMALL LETTER I WITH GRAVE -00ED: í LATIN SMALL LETTER I WITH ACUTE -00EE: î LATIN SMALL LETTER I WITH CIRCUMFLEX -00EF: ï LATIN SMALL LETTER I WITH DIAERESIS -00F0: ð LATIN SMALL LETTER ETH -00F1: ñ LATIN SMALL LETTER N WITH TILDE -00F2: ò LATIN SMALL LETTER O WITH GRAVE -00F3: ó LATIN SMALL LETTER O WITH ACUTE -00F4: ô LATIN SMALL LETTER O WITH CIRCUMFLEX -00F5: õ LATIN SMALL LETTER O WITH TILDE -00F6: ö LATIN SMALL LETTER O WITH DIAERESIS -00F7: ÷ DIVISION SIGN -00F8: ø LATIN SMALL LETTER O WITH STROKE -00F9: ù LATIN SMALL LETTER U WITH GRAVE -00FA: ú LATIN SMALL LETTER U WITH ACUTE -00FB: û LATIN SMALL LETTER U WITH CIRCUMFLEX -00FC: ü LATIN SMALL LETTER U WITH DIAERESIS -00FD: ý LATIN SMALL LETTER Y WITH ACUTE -00FE: þ LATIN SMALL LETTER THORN -00FF: ÿ LATIN SMALL LETTER Y WITH DIAERESIS - -# Latin Extended-A -0100: Ā LATIN CAPITAL LETTER A WITH MACRON -0101: ā LATIN SMALL LETTER A WITH MACRON -0102: Ă LATIN CAPITAL LETTER A WITH BREVE -0103: ă LATIN SMALL LETTER A WITH BREVE -0104: Ą LATIN CAPITAL LETTER A WITH OGONEK -0105: ą LATIN SMALL LETTER A WITH OGONEK -0106: Ć LATIN CAPITAL LETTER C WITH ACUTE -0107: ć LATIN SMALL LETTER C WITH ACUTE -0108: Ĉ LATIN CAPITAL LETTER C WITH CIRCUMFLEX -0109: ĉ LATIN SMALL LETTER C WITH CIRCUMFLEX -010A: Ċ LATIN CAPITAL LETTER C WITH DOT ABOVE -010B: ċ LATIN SMALL LETTER C WITH DOT ABOVE -010C: Č LATIN CAPITAL LETTER C WITH CARON -010D: č LATIN SMALL LETTER C WITH CARON -010E: Ď LATIN CAPITAL LETTER D WITH CARON -010F: ď LATIN SMALL LETTER D WITH CARON -0110: Đ LATIN CAPITAL LETTER D WITH STROKE -0111: đ LATIN SMALL LETTER D WITH STROKE -0112: Ē LATIN CAPITAL LETTER E WITH MACRON -0113: ē LATIN SMALL LETTER E WITH MACRON -0114: Ĕ LATIN CAPITAL LETTER E WITH BREVE -0115: ĕ LATIN SMALL LETTER E WITH BREVE -0116: Ė LATIN CAPITAL LETTER E WITH DOT ABOVE -0117: ė LATIN SMALL LETTER E WITH DOT ABOVE -0118: Ę LATIN CAPITAL LETTER E WITH OGONEK -0119: ę LATIN SMALL LETTER E WITH OGONEK -011A: Ě LATIN CAPITAL LETTER E WITH CARON -011B: ě LATIN SMALL LETTER E WITH CARON -011C: Ĝ LATIN CAPITAL LETTER G WITH CIRCUMFLEX -011D: ĝ LATIN SMALL LETTER G WITH CIRCUMFLEX -011E: Ğ LATIN CAPITAL LETTER G WITH BREVE -011F: ğ LATIN SMALL LETTER G WITH BREVE -0120: Ġ LATIN CAPITAL LETTER G WITH DOT ABOVE -0121: ġ LATIN SMALL LETTER G WITH DOT ABOVE -0122: Ģ LATIN CAPITAL LETTER G WITH CEDILLA -0123: ģ LATIN SMALL LETTER G WITH CEDILLA -0124: Ĥ LATIN CAPITAL LETTER H WITH CIRCUMFLEX -0125: ĥ LATIN SMALL LETTER H WITH CIRCUMFLEX -0126: Ħ LATIN CAPITAL LETTER H WITH STROKE -0127: ħ LATIN SMALL LETTER H WITH STROKE -0128: Ĩ LATIN CAPITAL LETTER I WITH TILDE -0129: ĩ LATIN SMALL LETTER I WITH TILDE -012A: Ī LATIN CAPITAL LETTER I WITH MACRON -012B: ī LATIN SMALL LETTER I WITH MACRON -012C: Ĭ LATIN CAPITAL LETTER I WITH BREVE -012D: ĭ LATIN SMALL LETTER I WITH BREVE -012E: Į LATIN CAPITAL LETTER I WITH OGONEK -012F: į LATIN SMALL LETTER I WITH OGONEK -0130: İ LATIN CAPITAL LETTER I WITH DOT ABOVE -0131: ı LATIN SMALL LETTER DOTLESS I -0132: IJ LATIN CAPITAL LIGATURE IJ -0133: ij LATIN SMALL LIGATURE IJ -0134: Ĵ LATIN CAPITAL LETTER J WITH CIRCUMFLEX -0135: ĵ LATIN SMALL LETTER J WITH CIRCUMFLEX -0136: Ķ LATIN CAPITAL LETTER K WITH CEDILLA -0137: ķ LATIN SMALL LETTER K WITH CEDILLA -0138: ĸ LATIN SMALL LETTER KRA -0139: Ĺ LATIN CAPITAL LETTER L WITH ACUTE -013A: ĺ LATIN SMALL LETTER L WITH ACUTE -013B: Ļ LATIN CAPITAL LETTER L WITH CEDILLA -013C: ļ LATIN SMALL LETTER L WITH CEDILLA -013D: Ľ LATIN CAPITAL LETTER L WITH CARON -013E: ľ LATIN SMALL LETTER L WITH CARON -013F: Ŀ LATIN CAPITAL LETTER L WITH MIDDLE DOT -0140: ŀ LATIN SMALL LETTER L WITH MIDDLE DOT -0141: Ł LATIN CAPITAL LETTER L WITH STROKE -0142: ł LATIN SMALL LETTER L WITH STROKE -0143: Ń LATIN CAPITAL LETTER N WITH ACUTE -0144: ń LATIN SMALL LETTER N WITH ACUTE -0145: Ņ LATIN CAPITAL LETTER N WITH CEDILLA -0146: ņ LATIN SMALL LETTER N WITH CEDILLA -0147: Ň LATIN CAPITAL LETTER N WITH CARON -0148: ň LATIN SMALL LETTER N WITH CARON -0149: ʼn LATIN SMALL LETTER N PRECEDED BY APOSTROPHE -014A: Ŋ LATIN CAPITAL LETTER ENG -014B: ŋ LATIN SMALL LETTER ENG -014C: Ō LATIN CAPITAL LETTER O WITH MACRON -014D: ō LATIN SMALL LETTER O WITH MACRON -014E: Ŏ LATIN CAPITAL LETTER O WITH BREVE -014F: ŏ LATIN SMALL LETTER O WITH BREVE -0150: Ő LATIN CAPITAL LETTER O WITH DOUBLE ACUTE -0151: ő LATIN SMALL LETTER O WITH DOUBLE ACUTE -0152: Œ LATIN CAPITAL LIGATURE OE -0153: œ LATIN SMALL LIGATURE OE -0154: Ŕ LATIN CAPITAL LETTER R WITH ACUTE -0155: ŕ LATIN SMALL LETTER R WITH ACUTE -0156: Ŗ LATIN CAPITAL LETTER R WITH CEDILLA -0157: ŗ LATIN SMALL LETTER R WITH CEDILLA -0158: Ř LATIN CAPITAL LETTER R WITH CARON -0159: ř LATIN SMALL LETTER R WITH CARON -015A: Ś LATIN CAPITAL LETTER S WITH ACUTE -015B: ś LATIN SMALL LETTER S WITH ACUTE -015C: Ŝ LATIN CAPITAL LETTER S WITH CIRCUMFLEX -015D: ŝ LATIN SMALL LETTER S WITH CIRCUMFLEX -015E: Ş LATIN CAPITAL LETTER S WITH CEDILLA -015F: ş LATIN SMALL LETTER S WITH CEDILLA -0160: Š LATIN CAPITAL LETTER S WITH CARON -0161: š LATIN SMALL LETTER S WITH CARON -0162: Ţ LATIN CAPITAL LETTER T WITH CEDILLA -0163: ţ LATIN SMALL LETTER T WITH CEDILLA -0164: Ť LATIN CAPITAL LETTER T WITH CARON -0165: ť LATIN SMALL LETTER T WITH CARON -0166: Ŧ LATIN CAPITAL LETTER T WITH STROKE -0167: ŧ LATIN SMALL LETTER T WITH STROKE -0168: Ũ LATIN CAPITAL LETTER U WITH TILDE -0169: ũ LATIN SMALL LETTER U WITH TILDE -016A: Ū LATIN CAPITAL LETTER U WITH MACRON -016B: ū LATIN SMALL LETTER U WITH MACRON -016C: Ŭ LATIN CAPITAL LETTER U WITH BREVE -016D: ŭ LATIN SMALL LETTER U WITH BREVE -016E: Ů LATIN CAPITAL LETTER U WITH RING ABOVE -016F: ů LATIN SMALL LETTER U WITH RING ABOVE -0170: Ű LATIN CAPITAL LETTER U WITH DOUBLE ACUTE -0171: ű LATIN SMALL LETTER U WITH DOUBLE ACUTE -0172: Ų LATIN CAPITAL LETTER U WITH OGONEK -0173: ų LATIN SMALL LETTER U WITH OGONEK -0174: Ŵ LATIN CAPITAL LETTER W WITH CIRCUMFLEX -0175: ŵ LATIN SMALL LETTER W WITH CIRCUMFLEX -0176: Ŷ LATIN CAPITAL LETTER Y WITH CIRCUMFLEX -0177: ŷ LATIN SMALL LETTER Y WITH CIRCUMFLEX -0178: Ÿ LATIN CAPITAL LETTER Y WITH DIAERESIS -0179: Ź LATIN CAPITAL LETTER Z WITH ACUTE -017A: ź LATIN SMALL LETTER Z WITH ACUTE -017B: Ż LATIN CAPITAL LETTER Z WITH DOT ABOVE -017C: ż LATIN SMALL LETTER Z WITH DOT ABOVE -017D: Ž LATIN CAPITAL LETTER Z WITH CARON -017E: ž LATIN SMALL LETTER Z WITH CARON -017F: ſ LATIN SMALL LETTER LONG S - -# Latin Extended-B -0180: ƀ LATIN SMALL LETTER B WITH STROKE -0181: Ɓ LATIN CAPITAL LETTER B WITH HOOK -0182: Ƃ LATIN CAPITAL LETTER B WITH TOPBAR -0183: ƃ LATIN SMALL LETTER B WITH TOPBAR -0184: Ƅ LATIN CAPITAL LETTER TONE SIX -0185: ƅ LATIN SMALL LETTER TONE SIX -0186: Ɔ LATIN CAPITAL LETTER OPEN O -0187: Ƈ LATIN CAPITAL LETTER C WITH HOOK -0188: ƈ LATIN SMALL LETTER C WITH HOOK -0189: Ɖ LATIN CAPITAL LETTER AFRICAN D -018A: Ɗ LATIN CAPITAL LETTER D WITH HOOK -018B: Ƌ LATIN CAPITAL LETTER D WITH TOPBAR -018C: ƌ LATIN SMALL LETTER D WITH TOPBAR -018D: ƍ LATIN SMALL LETTER TURNED DELTA -018E: Ǝ LATIN CAPITAL LETTER REVERSED E -018F: Ə LATIN CAPITAL LETTER SCHWA -0190: Ɛ LATIN CAPITAL LETTER OPEN E -0191: Ƒ LATIN CAPITAL LETTER F WITH HOOK -0192: ƒ LATIN SMALL LETTER F WITH HOOK -0193: Ɠ LATIN CAPITAL LETTER G WITH HOOK -0194: Ɣ LATIN CAPITAL LETTER GAMMA -0195: ƕ LATIN SMALL LETTER HV -0196: Ɩ LATIN CAPITAL LETTER IOTA -0197: Ɨ LATIN CAPITAL LETTER I WITH STROKE -0198: Ƙ LATIN CAPITAL LETTER K WITH HOOK -0199: ƙ LATIN SMALL LETTER K WITH HOOK -019A: ƚ LATIN SMALL LETTER L WITH BAR -019B: ƛ LATIN SMALL LETTER LAMBDA WITH STROKE -019C: Ɯ LATIN CAPITAL LETTER TURNED M -019D: Ɲ LATIN CAPITAL LETTER N WITH LEFT HOOK -019E: ƞ LATIN SMALL LETTER N WITH LONG RIGHT LEG -019F: Ɵ LATIN CAPITAL LETTER O WITH MIDDLE TILDE -01A0: Ơ LATIN CAPITAL LETTER O WITH HORN -01A1: ơ LATIN SMALL LETTER O WITH HORN -01A2: Ƣ LATIN CAPITAL LETTER OI -01A3: ƣ LATIN SMALL LETTER OI -01A4: Ƥ LATIN CAPITAL LETTER P WITH HOOK -01A5: ƥ LATIN SMALL LETTER P WITH HOOK -01A6: Ʀ LATIN LETTER YR -01A7: Ƨ LATIN CAPITAL LETTER TONE TWO -01A8: ƨ LATIN SMALL LETTER TONE TWO -01A9: Ʃ LATIN CAPITAL LETTER ESH -01AA: ƪ LATIN LETTER REVERSED ESH LOOP -01AB: ƫ LATIN SMALL LETTER T WITH PALATAL HOOK -01AC: Ƭ LATIN CAPITAL LETTER T WITH HOOK -01AD: ƭ LATIN SMALL LETTER T WITH HOOK -01AE: Ʈ LATIN CAPITAL LETTER T WITH RETROFLEX HOOK -01AF: Ư LATIN CAPITAL LETTER U WITH HORN -01B0: ư LATIN SMALL LETTER U WITH HORN -01B1: Ʊ LATIN CAPITAL LETTER UPSILON -01B2: Ʋ LATIN CAPITAL LETTER V WITH HOOK -01B3: Ƴ LATIN CAPITAL LETTER Y WITH HOOK -01B4: ƴ LATIN SMALL LETTER Y WITH HOOK -01B5: Ƶ LATIN CAPITAL LETTER Z WITH STROKE -01B6: ƶ LATIN SMALL LETTER Z WITH STROKE -01B7: Ʒ LATIN CAPITAL LETTER EZH -01B8: Ƹ LATIN CAPITAL LETTER EZH REVERSED -01B9: ƹ LATIN SMALL LETTER EZH REVERSED -01BA: ƺ LATIN SMALL LETTER EZH WITH TAIL -01BB: ƻ LATIN LETTER TWO WITH STROKE -01BC: Ƽ LATIN CAPITAL LETTER TONE FIVE -01BD: ƽ LATIN SMALL LETTER TONE FIVE -01BE: ƾ LATIN LETTER INVERTED GLOTTAL STOP WITH STROKE -01BF: ƿ LATIN LETTER WYNN -01C0: ǀ LATIN LETTER DENTAL CLICK -01C1: ǁ LATIN LETTER LATERAL CLICK -01C2: ǂ LATIN LETTER ALVEOLAR CLICK -01C3: ǃ LATIN LETTER RETROFLEX CLICK -01C4: DŽ LATIN CAPITAL LETTER DZ WITH CARON -01C5: Dž LATIN CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON -01C6: dž LATIN SMALL LETTER DZ WITH CARON -01C7: LJ LATIN CAPITAL LETTER LJ -01C8: Lj LATIN CAPITAL LETTER L WITH SMALL LETTER J -01C9: lj LATIN SMALL LETTER LJ -01CA: NJ LATIN CAPITAL LETTER NJ -01CB: Nj LATIN CAPITAL LETTER N WITH SMALL LETTER J -01CC: nj LATIN SMALL LETTER NJ -01CD: Ǎ LATIN CAPITAL LETTER A WITH CARON -01CE: ǎ LATIN SMALL LETTER A WITH CARON -01CF: Ǐ LATIN CAPITAL LETTER I WITH CARON -01D0: ǐ LATIN SMALL LETTER I WITH CARON -01D1: Ǒ LATIN CAPITAL LETTER O WITH CARON -01D2: ǒ LATIN SMALL LETTER O WITH CARON -01D3: Ǔ LATIN CAPITAL LETTER U WITH CARON -01D4: ǔ LATIN SMALL LETTER U WITH CARON -01D5: Ǖ LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON -01D6: ǖ LATIN SMALL LETTER U WITH DIAERESIS AND MACRON -01D7: Ǘ LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE -01D8: ǘ LATIN SMALL LETTER U WITH DIAERESIS AND ACUTE -01D9: Ǚ LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON -01DA: ǚ LATIN SMALL LETTER U WITH DIAERESIS AND CARON -01DB: Ǜ LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE -01DC: ǜ LATIN SMALL LETTER U WITH DIAERESIS AND GRAVE -01DD: ǝ LATIN SMALL LETTER TURNED E -01DE: Ǟ LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON -01DF: ǟ LATIN SMALL LETTER A WITH DIAERESIS AND MACRON -01E0: Ǡ LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON -01E1: ǡ LATIN SMALL LETTER A WITH DOT ABOVE AND MACRON -01E2: Ǣ LATIN CAPITAL LETTER AE WITH MACRON -01E3: ǣ LATIN SMALL LETTER AE WITH MACRON -01E4: Ǥ LATIN CAPITAL LETTER G WITH STROKE -01E5: ǥ LATIN SMALL LETTER G WITH STROKE -01E6: Ǧ LATIN CAPITAL LETTER G WITH CARON -01E7: ǧ LATIN SMALL LETTER G WITH CARON -01E8: Ǩ LATIN CAPITAL LETTER K WITH CARON -01E9: ǩ LATIN SMALL LETTER K WITH CARON -01EA: Ǫ LATIN CAPITAL LETTER O WITH OGONEK -01EB: ǫ LATIN SMALL LETTER O WITH OGONEK -01EC: Ǭ LATIN CAPITAL LETTER O WITH OGONEK AND MACRON -01ED: ǭ LATIN SMALL LETTER O WITH OGONEK AND MACRON -01EE: Ǯ LATIN CAPITAL LETTER EZH WITH CARON -01EF: ǯ LATIN SMALL LETTER EZH WITH CARON -01F0: ǰ LATIN SMALL LETTER J WITH CARON -01F1: DZ LATIN CAPITAL LETTER DZ -01F2: Dz LATIN CAPITAL LETTER D WITH SMALL LETTER Z -01F3: dz LATIN SMALL LETTER DZ -01F4: Ǵ LATIN CAPITAL LETTER G WITH ACUTE -01F5: ǵ LATIN SMALL LETTER G WITH ACUTE -01F6: Ƕ LATIN CAPITAL LETTER HWAIR -01F7: Ƿ LATIN CAPITAL LETTER WYNN -01F8: Ǹ LATIN CAPITAL LETTER N WITH GRAVE -01F9: ǹ LATIN SMALL LETTER N WITH GRAVE -01FA: Ǻ LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE -01FB: ǻ LATIN SMALL LETTER A WITH RING ABOVE AND ACUTE -01FC: Ǽ LATIN CAPITAL LETTER AE WITH ACUTE -01FD: ǽ LATIN SMALL LETTER AE WITH ACUTE -01FE: Ǿ LATIN CAPITAL LETTER O WITH STROKE AND ACUTE -01FF: ǿ LATIN SMALL LETTER O WITH STROKE AND ACUTE -0200: Ȁ LATIN CAPITAL LETTER A WITH DOUBLE GRAVE -0201: ȁ LATIN SMALL LETTER A WITH DOUBLE GRAVE -0202: Ȃ LATIN CAPITAL LETTER A WITH INVERTED BREVE -0203: ȃ LATIN SMALL LETTER A WITH INVERTED BREVE -0204: Ȅ LATIN CAPITAL LETTER E WITH DOUBLE GRAVE -0205: ȅ LATIN SMALL LETTER E WITH DOUBLE GRAVE -0206: Ȇ LATIN CAPITAL LETTER E WITH INVERTED BREVE -0207: ȇ LATIN SMALL LETTER E WITH INVERTED BREVE -0208: Ȉ LATIN CAPITAL LETTER I WITH DOUBLE GRAVE -0209: ȉ LATIN SMALL LETTER I WITH DOUBLE GRAVE -020A: Ȋ LATIN CAPITAL LETTER I WITH INVERTED BREVE -020B: ȋ LATIN SMALL LETTER I WITH INVERTED BREVE -020C: Ȍ LATIN CAPITAL LETTER O WITH DOUBLE GRAVE -020D: ȍ LATIN SMALL LETTER O WITH DOUBLE GRAVE -020E: Ȏ LATIN CAPITAL LETTER O WITH INVERTED BREVE -020F: ȏ LATIN SMALL LETTER O WITH INVERTED BREVE -0210: Ȑ LATIN CAPITAL LETTER R WITH DOUBLE GRAVE -0211: ȑ LATIN SMALL LETTER R WITH DOUBLE GRAVE -0212: Ȓ LATIN CAPITAL LETTER R WITH INVERTED BREVE -0213: ȓ LATIN SMALL LETTER R WITH INVERTED BREVE -0214: Ȕ LATIN CAPITAL LETTER U WITH DOUBLE GRAVE -0215: ȕ LATIN SMALL LETTER U WITH DOUBLE GRAVE -0216: Ȗ LATIN CAPITAL LETTER U WITH INVERTED BREVE -0217: ȗ LATIN SMALL LETTER U WITH INVERTED BREVE -0218: Ș LATIN CAPITAL LETTER S WITH COMMA BELOW -0219: ș LATIN SMALL LETTER S WITH COMMA BELOW -021A: Ț LATIN CAPITAL LETTER T WITH COMMA BELOW -021B: ț LATIN SMALL LETTER T WITH COMMA BELOW -021C: Ȝ LATIN CAPITAL LETTER YOGH -021D: ȝ LATIN SMALL LETTER YOGH -021E: Ȟ LATIN CAPITAL LETTER H WITH CARON -021F: ȟ LATIN SMALL LETTER H WITH CARON -0220: Ƞ LATIN CAPITAL LETTER N WITH LONG RIGHT LEG -0221: ȡ LATIN SMALL LETTER D WITH CURL -0222: Ȣ LATIN CAPITAL LETTER OU -0223: ȣ LATIN SMALL LETTER OU -0224: Ȥ LATIN CAPITAL LETTER Z WITH HOOK -0225: ȥ LATIN SMALL LETTER Z WITH HOOK -0226: Ȧ LATIN CAPITAL LETTER A WITH DOT ABOVE -0227: ȧ LATIN SMALL LETTER A WITH DOT ABOVE -0228: Ȩ LATIN CAPITAL LETTER E WITH CEDILLA -0229: ȩ LATIN SMALL LETTER E WITH CEDILLA -022A: Ȫ LATIN CAPITAL LETTER O WITH DIAERESIS AND MACRON -022B: ȫ LATIN SMALL LETTER O WITH DIAERESIS AND MACRON -022C: Ȭ LATIN CAPITAL LETTER O WITH TILDE AND MACRON -022D: ȭ LATIN SMALL LETTER O WITH TILDE AND MACRON -022E: Ȯ LATIN CAPITAL LETTER O WITH DOT ABOVE -022F: ȯ LATIN SMALL LETTER O WITH DOT ABOVE -0230: Ȱ LATIN CAPITAL LETTER O WITH DOT ABOVE AND MACRON -0231: ȱ LATIN SMALL LETTER O WITH DOT ABOVE AND MACRON -0232: Ȳ LATIN CAPITAL LETTER Y WITH MACRON -0233: ȳ LATIN SMALL LETTER Y WITH MACRON -0234: ȴ LATIN SMALL LETTER L WITH CURL -0235: ȵ LATIN SMALL LETTER N WITH CURL -0236: ȶ LATIN SMALL LETTER T WITH CURL -0237: ȷ LATIN SMALL LETTER DOTLESS J -0238: ȸ LATIN SMALL LETTER DB DIGRAPH -0239: ȹ LATIN SMALL LETTER QP DIGRAPH -023A: Ⱥ LATIN CAPITAL LETTER A WITH STROKE -023B: Ȼ LATIN CAPITAL LETTER C WITH STROKE -023C: ȼ LATIN SMALL LETTER C WITH STROKE -023D: Ƚ LATIN CAPITAL LETTER L WITH BAR -023E: Ⱦ LATIN CAPITAL LETTER T WITH DIAGONAL STROKE -023F: ȿ LATIN SMALL LETTER S WITH SWASH TAIL -0240: ɀ LATIN SMALL LETTER Z WITH SWASH TAIL -0241: Ɂ LATIN CAPITAL LETTER GLOTTAL STOP -0242: ɂ LATIN SMALL LETTER GLOTTAL STOP -0243: Ƀ LATIN CAPITAL LETTER B WITH STROKE -0244: Ʉ LATIN CAPITAL LETTER U BAR -0245: Ʌ LATIN CAPITAL LETTER TURNED V -0246: Ɇ LATIN CAPITAL LETTER E WITH STROKE -0247: ɇ LATIN SMALL LETTER E WITH STROKE -0248: Ɉ LATIN CAPITAL LETTER J WITH STROKE -0249: ɉ LATIN SMALL LETTER J WITH STROKE -024A: Ɋ LATIN CAPITAL LETTER SMALL Q WITH HOOK TAIL -024B: ɋ LATIN SMALL LETTER Q WITH HOOK TAIL -024C: Ɍ LATIN CAPITAL LETTER R WITH STROKE -024D: ɍ LATIN SMALL LETTER R WITH STROKE -024E: Ɏ LATIN CAPITAL LETTER Y WITH STROKE -024F: ɏ LATIN SMALL LETTER Y WITH STROKE - -# Latin Extended Additional -1E00: Ḁ LATIN CAPITAL LETTER A WITH RING BELOW -1E01: ḁ LATIN SMALL LETTER A WITH RING BELOW -1E02: Ḃ LATIN CAPITAL LETTER B WITH DOT ABOVE -1E03: ḃ LATIN SMALL LETTER B WITH DOT ABOVE -1E04: Ḅ LATIN CAPITAL LETTER B WITH DOT BELOW -1E05: ḅ LATIN SMALL LETTER B WITH DOT BELOW -1E06: Ḇ LATIN CAPITAL LETTER B WITH LINE BELOW -1E07: ḇ LATIN SMALL LETTER B WITH LINE BELOW -1E08: Ḉ LATIN CAPITAL LETTER C WITH CEDILLA AND ACUTE -1E09: ḉ LATIN SMALL LETTER C WITH CEDILLA AND ACUTE -1E0A: Ḋ LATIN CAPITAL LETTER D WITH DOT ABOVE -1E0B: ḋ LATIN SMALL LETTER D WITH DOT ABOVE -1E0C: Ḍ LATIN CAPITAL LETTER D WITH DOT BELOW -1E0D: ḍ LATIN SMALL LETTER D WITH DOT BELOW -1E0E: Ḏ LATIN CAPITAL LETTER D WITH LINE BELOW -1E0F: ḏ LATIN SMALL LETTER D WITH LINE BELOW -1E10: Ḑ LATIN CAPITAL LETTER D WITH CEDILLA -1E11: ḑ LATIN SMALL LETTER D WITH CEDILLA -1E12: Ḓ LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW -1E13: ḓ LATIN SMALL LETTER D WITH CIRCUMFLEX BELOW -1E14: Ḕ LATIN CAPITAL LETTER E WITH MACRON AND GRAVE -1E15: ḕ LATIN SMALL LETTER E WITH MACRON AND GRAVE -1E16: Ḗ LATIN CAPITAL LETTER E WITH MACRON AND ACUTE -1E17: ḗ LATIN SMALL LETTER E WITH MACRON AND ACUTE -1E18: Ḙ LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW -1E19: ḙ LATIN SMALL LETTER E WITH CIRCUMFLEX BELOW -1E1A: Ḛ LATIN CAPITAL LETTER E WITH TILDE BELOW -1E1B: ḛ LATIN SMALL LETTER E WITH TILDE BELOW -1E1C: Ḝ LATIN CAPITAL LETTER E WITH CEDILLA AND BREVE -1E1D: ḝ LATIN SMALL LETTER E WITH CEDILLA AND BREVE -1E1E: Ḟ LATIN CAPITAL LETTER F WITH DOT ABOVE -1E1F: ḟ LATIN SMALL LETTER F WITH DOT ABOVE -1E20: Ḡ LATIN CAPITAL LETTER G WITH MACRON -1E21: ḡ LATIN SMALL LETTER G WITH MACRON -1E22: Ḣ LATIN CAPITAL LETTER H WITH DOT ABOVE -1E23: ḣ LATIN SMALL LETTER H WITH DOT ABOVE -1E24: Ḥ LATIN CAPITAL LETTER H WITH DOT BELOW -1E25: ḥ LATIN SMALL LETTER H WITH DOT BELOW -1E26: Ḧ LATIN CAPITAL LETTER H WITH DIAERESIS -1E27: ḧ LATIN SMALL LETTER H WITH DIAERESIS -1E28: Ḩ LATIN CAPITAL LETTER H WITH CEDILLA -1E29: ḩ LATIN SMALL LETTER H WITH CEDILLA -1E2A: Ḫ LATIN CAPITAL LETTER H WITH BREVE BELOW -1E2B: ḫ LATIN SMALL LETTER H WITH BREVE BELOW -1E2C: Ḭ LATIN CAPITAL LETTER I WITH TILDE BELOW -1E2D: ḭ LATIN SMALL LETTER I WITH TILDE BELOW -1E2E: Ḯ LATIN CAPITAL LETTER I WITH DIAERESIS AND ACUTE -1E2F: ḯ LATIN SMALL LETTER I WITH DIAERESIS AND ACUTE -1E30: Ḱ LATIN CAPITAL LETTER K WITH ACUTE -1E31: ḱ LATIN SMALL LETTER K WITH ACUTE -1E32: Ḳ LATIN CAPITAL LETTER K WITH DOT BELOW -1E33: ḳ LATIN SMALL LETTER K WITH DOT BELOW -1E34: Ḵ LATIN CAPITAL LETTER K WITH LINE BELOW -1E35: ḵ LATIN SMALL LETTER K WITH LINE BELOW -1E36: Ḷ LATIN CAPITAL LETTER L WITH DOT BELOW -1E37: ḷ LATIN SMALL LETTER L WITH DOT BELOW -1E38: Ḹ LATIN CAPITAL LETTER L WITH DOT BELOW AND MACRON -1E39: ḹ LATIN SMALL LETTER L WITH DOT BELOW AND MACRON -1E3A: Ḻ LATIN CAPITAL LETTER L WITH LINE BELOW -1E3B: ḻ LATIN SMALL LETTER L WITH LINE BELOW -1E3C: Ḽ LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW -1E3D: ḽ LATIN SMALL LETTER L WITH CIRCUMFLEX BELOW -1E3E: Ḿ LATIN CAPITAL LETTER M WITH ACUTE -1E3F: ḿ LATIN SMALL LETTER M WITH ACUTE -1E40: Ṁ LATIN CAPITAL LETTER M WITH DOT ABOVE -1E41: ṁ LATIN SMALL LETTER M WITH DOT ABOVE -1E42: Ṃ LATIN CAPITAL LETTER M WITH DOT BELOW -1E43: ṃ LATIN SMALL LETTER M WITH DOT BELOW -1E44: Ṅ LATIN CAPITAL LETTER N WITH DOT ABOVE -1E45: ṅ LATIN SMALL LETTER N WITH DOT ABOVE -1E46: Ṇ LATIN CAPITAL LETTER N WITH DOT BELOW -1E47: ṇ LATIN SMALL LETTER N WITH DOT BELOW -1E48: Ṉ LATIN CAPITAL LETTER N WITH LINE BELOW -1E49: ṉ LATIN SMALL LETTER N WITH LINE BELOW -1E4A: Ṋ LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW -1E4B: ṋ LATIN SMALL LETTER N WITH CIRCUMFLEX BELOW -1E4C: Ṍ LATIN CAPITAL LETTER O WITH TILDE AND ACUTE -1E4D: ṍ LATIN SMALL LETTER O WITH TILDE AND ACUTE -1E4E: Ṏ LATIN CAPITAL LETTER O WITH TILDE AND DIAERESIS -1E4F: ṏ LATIN SMALL LETTER O WITH TILDE AND DIAERESIS -1E50: Ṑ LATIN CAPITAL LETTER O WITH MACRON AND GRAVE -1E51: ṑ LATIN SMALL LETTER O WITH MACRON AND GRAVE -1E52: Ṓ LATIN CAPITAL LETTER O WITH MACRON AND ACUTE -1E53: ṓ LATIN SMALL LETTER O WITH MACRON AND ACUTE -1E54: Ṕ LATIN CAPITAL LETTER P WITH ACUTE -1E55: ṕ LATIN SMALL LETTER P WITH ACUTE -1E56: Ṗ LATIN CAPITAL LETTER P WITH DOT ABOVE -1E57: ṗ LATIN SMALL LETTER P WITH DOT ABOVE -1E58: Ṙ LATIN CAPITAL LETTER R WITH DOT ABOVE -1E59: ṙ LATIN SMALL LETTER R WITH DOT ABOVE -1E5A: Ṛ LATIN CAPITAL LETTER R WITH DOT BELOW -1E5B: ṛ LATIN SMALL LETTER R WITH DOT BELOW -1E5C: Ṝ LATIN CAPITAL LETTER R WITH DOT BELOW AND MACRON -1E5D: ṝ LATIN SMALL LETTER R WITH DOT BELOW AND MACRON -1E5E: Ṟ LATIN CAPITAL LETTER R WITH LINE BELOW -1E5F: ṟ LATIN SMALL LETTER R WITH LINE BELOW -1E60: Ṡ LATIN CAPITAL LETTER S WITH DOT ABOVE -1E61: ṡ LATIN SMALL LETTER S WITH DOT ABOVE -1E62: Ṣ LATIN CAPITAL LETTER S WITH DOT BELOW -1E63: ṣ LATIN SMALL LETTER S WITH DOT BELOW -1E64: Ṥ LATIN CAPITAL LETTER S WITH ACUTE AND DOT ABOVE -1E65: ṥ LATIN SMALL LETTER S WITH ACUTE AND DOT ABOVE -1E66: Ṧ LATIN CAPITAL LETTER S WITH CARON AND DOT ABOVE -1E67: ṧ LATIN SMALL LETTER S WITH CARON AND DOT ABOVE -1E68: Ṩ LATIN CAPITAL LETTER S WITH DOT BELOW AND DOT ABOVE -1E69: ṩ LATIN SMALL LETTER S WITH DOT BELOW AND DOT ABOVE -1E6A: Ṫ LATIN CAPITAL LETTER T WITH DOT ABOVE -1E6B: ṫ LATIN SMALL LETTER T WITH DOT ABOVE -1E6C: Ṭ LATIN CAPITAL LETTER T WITH DOT BELOW -1E6D: ṭ LATIN SMALL LETTER T WITH DOT BELOW -1E6E: Ṯ LATIN CAPITAL LETTER T WITH LINE BELOW -1E6F: ṯ LATIN SMALL LETTER T WITH LINE BELOW -1E70: Ṱ LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW -1E71: ṱ LATIN SMALL LETTER T WITH CIRCUMFLEX BELOW -1E72: Ṳ LATIN CAPITAL LETTER U WITH DIAERESIS BELOW -1E73: ṳ LATIN SMALL LETTER U WITH DIAERESIS BELOW -1E74: Ṵ LATIN CAPITAL LETTER U WITH TILDE BELOW -1E75: ṵ LATIN SMALL LETTER U WITH TILDE BELOW -1E76: Ṷ LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW -1E77: ṷ LATIN SMALL LETTER U WITH CIRCUMFLEX BELOW -1E78: Ṹ LATIN CAPITAL LETTER U WITH TILDE AND ACUTE -1E79: ṹ LATIN SMALL LETTER U WITH TILDE AND ACUTE -1E7A: Ṻ LATIN CAPITAL LETTER U WITH MACRON AND DIAERESIS -1E7B: ṻ LATIN SMALL LETTER U WITH MACRON AND DIAERESIS -1E7C: Ṽ LATIN CAPITAL LETTER V WITH TILDE -1E7D: ṽ LATIN SMALL LETTER V WITH TILDE -1E7E: Ṿ LATIN CAPITAL LETTER V WITH DOT BELOW -1E7F: ṿ LATIN SMALL LETTER V WITH DOT BELOW -1E80: Ẁ LATIN CAPITAL LETTER W WITH GRAVE -1E81: ẁ LATIN SMALL LETTER W WITH GRAVE -1E82: Ẃ LATIN CAPITAL LETTER W WITH ACUTE -1E83: ẃ LATIN SMALL LETTER W WITH ACUTE -1E84: Ẅ LATIN CAPITAL LETTER W WITH DIAERESIS -1E85: ẅ LATIN SMALL LETTER W WITH DIAERESIS -1E86: Ẇ LATIN CAPITAL LETTER W WITH DOT ABOVE -1E87: ẇ LATIN SMALL LETTER W WITH DOT ABOVE -1E88: Ẉ LATIN CAPITAL LETTER W WITH DOT BELOW -1E89: ẉ LATIN SMALL LETTER W WITH DOT BELOW -1E8A: Ẋ LATIN CAPITAL LETTER X WITH DOT ABOVE -1E8B: ẋ LATIN SMALL LETTER X WITH DOT ABOVE -1E8C: Ẍ LATIN CAPITAL LETTER X WITH DIAERESIS -1E8D: ẍ LATIN SMALL LETTER X WITH DIAERESIS -1E8E: Ẏ LATIN CAPITAL LETTER Y WITH DOT ABOVE -1E8F: ẏ LATIN SMALL LETTER Y WITH DOT ABOVE -1E90: Ẑ LATIN CAPITAL LETTER Z WITH CIRCUMFLEX -1E91: ẑ LATIN SMALL LETTER Z WITH CIRCUMFLEX -1E92: Ẓ LATIN CAPITAL LETTER Z WITH DOT BELOW -1E93: ẓ LATIN SMALL LETTER Z WITH DOT BELOW -1E94: Ẕ LATIN CAPITAL LETTER Z WITH LINE BELOW -1E95: ẕ LATIN SMALL LETTER Z WITH LINE BELOW -1E96: ẖ LATIN SMALL LETTER H WITH LINE BELOW -1E97: ẗ LATIN SMALL LETTER T WITH DIAERESIS -1E98: ẘ LATIN SMALL LETTER W WITH RING ABOVE -1E99: ẙ LATIN SMALL LETTER Y WITH RING ABOVE -1E9A: ẚ LATIN SMALL LETTER A WITH RIGHT HALF RING -1E9B: ẛ LATIN SMALL LETTER LONG S WITH DOT ABOVE -1E9C: ẜ LATIN SMALL LETTER LONG S WITH DIAGONAL STROKE -1E9D: ẝ LATIN SMALL LETTER LONG S WITH HIGH STROKE -1E9E: ẞ LATIN CAPITAL LETTER SHARP S -1E9F: ẟ LATIN SMALL LETTER DELTA -1EA0: Ạ LATIN CAPITAL LETTER A WITH DOT BELOW -1EA1: ạ LATIN SMALL LETTER A WITH DOT BELOW -1EA2: Ả LATIN CAPITAL LETTER A WITH HOOK ABOVE -1EA3: ả LATIN SMALL LETTER A WITH HOOK ABOVE -1EA4: Ấ LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE -1EA5: ấ LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE -1EA6: Ầ LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE -1EA7: ầ LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE -1EA8: Ẩ LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE -1EA9: ẩ LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE -1EAA: Ẫ LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE -1EAB: ẫ LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE -1EAC: Ậ LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW -1EAD: ậ LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW -1EAE: Ắ LATIN CAPITAL LETTER A WITH BREVE AND ACUTE -1EAF: ắ LATIN SMALL LETTER A WITH BREVE AND ACUTE -1EB0: Ằ LATIN CAPITAL LETTER A WITH BREVE AND GRAVE -1EB1: ằ LATIN SMALL LETTER A WITH BREVE AND GRAVE -1EB2: Ẳ LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE -1EB3: ẳ LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE -1EB4: Ẵ LATIN CAPITAL LETTER A WITH BREVE AND TILDE -1EB5: ẵ LATIN SMALL LETTER A WITH BREVE AND TILDE -1EB6: Ặ LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW -1EB7: ặ LATIN SMALL LETTER A WITH BREVE AND DOT BELOW -1EB8: Ẹ LATIN CAPITAL LETTER E WITH DOT BELOW -1EB9: ẹ LATIN SMALL LETTER E WITH DOT BELOW -1EBA: Ẻ LATIN CAPITAL LETTER E WITH HOOK ABOVE -1EBB: ẻ LATIN SMALL LETTER E WITH HOOK ABOVE -1EBC: Ẽ LATIN CAPITAL LETTER E WITH TILDE -1EBD: ẽ LATIN SMALL LETTER E WITH TILDE -1EBE: Ế LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE -1EBF: ế LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE -1EC0: Ề LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE -1EC1: ề LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE -1EC2: Ể LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE -1EC3: ể LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE -1EC4: Ễ LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE -1EC5: ễ LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE -1EC6: Ệ LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW -1EC7: ệ LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW -1EC8: Ỉ LATIN CAPITAL LETTER I WITH HOOK ABOVE -1EC9: ỉ LATIN SMALL LETTER I WITH HOOK ABOVE -1ECA: Ị LATIN CAPITAL LETTER I WITH DOT BELOW -1ECB: ị LATIN SMALL LETTER I WITH DOT BELOW -1ECC: Ọ LATIN CAPITAL LETTER O WITH DOT BELOW -1ECD: ọ LATIN SMALL LETTER O WITH DOT BELOW -1ECE: Ỏ LATIN CAPITAL LETTER O WITH HOOK ABOVE -1ECF: ỏ LATIN SMALL LETTER O WITH HOOK ABOVE -1ED0: Ố LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE -1ED1: ố LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACUTE -1ED2: Ồ LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE -1ED3: ồ LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRAVE -1ED4: Ổ LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE -1ED5: ổ LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE -1ED6: Ỗ LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE -1ED7: ỗ LATIN SMALL LETTER O WITH CIRCUMFLEX AND TILDE -1ED8: Ộ LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW -1ED9: ộ LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW -1EDA: Ớ LATIN CAPITAL LETTER O WITH HORN AND ACUTE -1EDB: ớ LATIN SMALL LETTER O WITH HORN AND ACUTE -1EDC: Ờ LATIN CAPITAL LETTER O WITH HORN AND GRAVE -1EDD: ờ LATIN SMALL LETTER O WITH HORN AND GRAVE -1EDE: Ở LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE -1EDF: ở LATIN SMALL LETTER O WITH HORN AND HOOK ABOVE -1EE0: Ỡ LATIN CAPITAL LETTER O WITH HORN AND TILDE -1EE1: ỡ LATIN SMALL LETTER O WITH HORN AND TILDE -1EE2: Ợ LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW -1EE3: ợ LATIN SMALL LETTER O WITH HORN AND DOT BELOW -1EE4: Ụ LATIN CAPITAL LETTER U WITH DOT BELOW -1EE5: ụ LATIN SMALL LETTER U WITH DOT BELOW -1EE6: Ủ LATIN CAPITAL LETTER U WITH HOOK ABOVE -1EE7: ủ LATIN SMALL LETTER U WITH HOOK ABOVE -1EE8: Ứ LATIN CAPITAL LETTER U WITH HORN AND ACUTE -1EE9: ứ LATIN SMALL LETTER U WITH HORN AND ACUTE -1EEA: Ừ LATIN CAPITAL LETTER U WITH HORN AND GRAVE -1EEB: ừ LATIN SMALL LETTER U WITH HORN AND GRAVE -1EEC: Ử LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE -1EED: ử LATIN SMALL LETTER U WITH HORN AND HOOK ABOVE -1EEE: Ữ LATIN CAPITAL LETTER U WITH HORN AND TILDE -1EEF: ữ LATIN SMALL LETTER U WITH HORN AND TILDE -1EF0: Ự LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW -1EF1: ự LATIN SMALL LETTER U WITH HORN AND DOT BELOW -1EF2: Ỳ LATIN CAPITAL LETTER Y WITH GRAVE -1EF3: ỳ LATIN SMALL LETTER Y WITH GRAVE -1EF4: Ỵ LATIN CAPITAL LETTER Y WITH DOT BELOW -1EF5: ỵ LATIN SMALL LETTER Y WITH DOT BELOW -1EF6: Ỷ LATIN CAPITAL LETTER Y WITH HOOK ABOVE -1EF7: ỷ LATIN SMALL LETTER Y WITH HOOK ABOVE -1EF8: Ỹ LATIN CAPITAL LETTER Y WITH TILDE -1EF9: ỹ LATIN SMALL LETTER Y WITH TILDE -1EFA: Ỻ LATIN CAPITAL LETTER MIDDLE-WELSH LL -1EFB: ỻ LATIN SMALL LETTER MIDDLE-WELSH LL -1EFC: Ỽ LATIN CAPITAL LETTER MIDDLE-WELSH V -1EFD: ỽ LATIN SMALL LETTER MIDDLE-WELSH V -1EFE: Ỿ LATIN CAPITAL LETTER Y WITH LOOP -1EFF: ỿ LATIN SMALL LETTER Y WITH LOOP - -# Spacing Modifier Letters -02B0: ʰ MODIFIER LETTER SMALL H -02B1: ʱ MODIFIER LETTER SMALL H WITH HOOK -02B2: ʲ MODIFIER LETTER SMALL J -02B3: ʳ MODIFIER LETTER SMALL R -02B4: ʴ MODIFIER LETTER SMALL TURNED R -02B5: ʵ MODIFIER LETTER SMALL TURNED R WITH HOOK -02B6: ʶ MODIFIER LETTER SMALL CAPITAL INVERTED R -02B7: ʷ MODIFIER LETTER SMALL W -02B8: ʸ MODIFIER LETTER SMALL Y -02B9: ʹ MODIFIER LETTER PRIME -02BA: ʺ MODIFIER LETTER DOUBLE PRIME -02BB: ʻ MODIFIER LETTER TURNED COMMA -02BC: ʼ MODIFIER LETTER APOSTROPHE -02BD: ʽ MODIFIER LETTER REVERSED COMMA -02BE: ʾ MODIFIER LETTER RIGHT HALF RING -02BF: ʿ MODIFIER LETTER LEFT HALF RING -02C0: ˀ MODIFIER LETTER GLOTTAL STOP -02C1: ˁ MODIFIER LETTER REVERSED GLOTTAL STOP -02C2: ˂ MODIFIER LETTER LEFT ARROWHEAD -02C3: ˃ MODIFIER LETTER RIGHT ARROWHEAD -02C4: ˄ MODIFIER LETTER UP ARROWHEAD -02C5: ˅ MODIFIER LETTER DOWN ARROWHEAD -02C6: ˆ MODIFIER LETTER CIRCUMFLEX ACCENT -02C7: ˇ CARON -02C8: ˈ MODIFIER LETTER VERTICAL LINE -02C9: ˉ MODIFIER LETTER MACRON -02CA: ˊ MODIFIER LETTER ACUTE ACCENT -02CB: ˋ MODIFIER LETTER GRAVE ACCENT -02CC: ˌ MODIFIER LETTER LOW VERTICAL LINE -02CD: ˍ MODIFIER LETTER LOW MACRON -02CE: ˎ MODIFIER LETTER LOW GRAVE ACCENT -02CF: ˏ MODIFIER LETTER LOW ACUTE ACCENT -02D0: ː MODIFIER LETTER TRIANGULAR COLON -02D1: ˑ MODIFIER LETTER HALF TRIANGULAR COLON -02D2: ˒ MODIFIER LETTER CENTRED RIGHT HALF RING -02D3: ˓ MODIFIER LETTER CENTRED LEFT HALF RING -02D4: ˔ MODIFIER LETTER UP TACK -02D5: ˕ MODIFIER LETTER DOWN TACK -02D6: ˖ MODIFIER LETTER PLUS SIGN -02D7: ˗ MODIFIER LETTER MINUS SIGN -02D8: ˘ BREVE -02D9: ˙ DOT ABOVE -02DA: ˚ RING ABOVE -02DB: ˛ OGONEK -02DC: ˜ SMALL TILDE -02DD: ˝ DOUBLE ACUTE ACCENT -02DE: ˞ MODIFIER LETTER RHOTIC HOOK -02DF: ˟ MODIFIER LETTER CROSS ACCENT -02E0: ˠ MODIFIER LETTER SMALL GAMMA -02E1: ˡ MODIFIER LETTER SMALL L -02E2: ˢ MODIFIER LETTER SMALL S -02E3: ˣ MODIFIER LETTER SMALL X -02E4: ˤ MODIFIER LETTER SMALL REVERSED GLOTTAL STOP -02E5: ˥ MODIFIER LETTER EXTRA-HIGH TONE BAR -02E6: ˦ MODIFIER LETTER HIGH TONE BAR -02E7: ˧ MODIFIER LETTER MID TONE BAR -02E8: ˨ MODIFIER LETTER LOW TONE BAR -02E9: ˩ MODIFIER LETTER EXTRA-LOW TONE BAR -02EA: ˪ MODIFIER LETTER YIN DEPARTING TONE MARK -02EB: ˫ MODIFIER LETTER YANG DEPARTING TONE MARK -02EC: ˬ MODIFIER LETTER VOICING -02ED: ˭ MODIFIER LETTER UNASPIRATED -02EE: ˮ MODIFIER LETTER DOUBLE APOSTROPHE -02EF: ˯ MODIFIER LETTER LOW DOWN ARROWHEAD -02F0: ˰ MODIFIER LETTER LOW UP ARROWHEAD -02F1: ˱ MODIFIER LETTER LOW LEFT ARROWHEAD -02F2: ˲ MODIFIER LETTER LOW RIGHT ARROWHEAD -02F3: ˳ MODIFIER LETTER LOW RING -02F4: ˴ MODIFIER LETTER MIDDLE GRAVE ACCENT -02F5: ˵ MODIFIER LETTER MIDDLE DOUBLE GRAVE ACCENT -02F6: ˶ MODIFIER LETTER MIDDLE DOUBLE ACUTE ACCENT -02F7: ˷ MODIFIER LETTER LOW TILDE -02F8: ˸ MODIFIER LETTER RAISED COLON -02F9: ˹ MODIFIER LETTER BEGIN HIGH TONE -02FA: ˺ MODIFIER LETTER END HIGH TONE -02FB: ˻ MODIFIER LETTER BEGIN LOW TONE -02FC: ˼ MODIFIER LETTER END LOW TONE -02FD: ˽ MODIFIER LETTER SHELF -02FE: ˾ MODIFIER LETTER OPEN SHELF -02FF: ˿ MODIFIER LETTER LOW LEFT ARROW - -# General Punctuation -2000:   EN QUAD -2001:   EM QUAD -2002:   EN SPACE -2003:   EM SPACE -2004:   THREE-PER-EM SPACE -2005:   FOUR-PER-EM SPACE -2006:   SIX-PER-EM SPACE -2007:   FIGURE SPACE -2008:   PUNCTUATION SPACE -2009:   THIN SPACE -200A:   HAIR SPACE -200B: ​ ZERO WIDTH SPACE -200C: ‌ ZERO WIDTH NON-JOINER -200D: ‍ ZERO WIDTH JOINER -200E: ‎ LEFT-TO-RIGHT MARK -200F: ‏ RIGHT-TO-LEFT MARK -2010: ‐ HYPHEN -2011: ‑ NON-BREAKING HYPHEN -2012: ‒ FIGURE DASH -2013: – EN DASH -2014: — EM DASH -2015: ― HORIZONTAL BAR -2016: ‖ DOUBLE VERTICAL LINE -2017: ‗ DOUBLE LOW LINE -2018: ‘ LEFT SINGLE QUOTATION MARK -2019: ’ RIGHT SINGLE QUOTATION MARK -201A: ‚ SINGLE LOW-9 QUOTATION MARK -201B: ‛ SINGLE HIGH-REVERSED-9 QUOTATION MARK -201C: “ LEFT DOUBLE QUOTATION MARK -201D: ” RIGHT DOUBLE QUOTATION MARK -201E: „ DOUBLE LOW-9 QUOTATION MARK -201F: ‟ DOUBLE HIGH-REVERSED-9 QUOTATION MARK -2020: † DAGGER -2021: ‡ DOUBLE DAGGER -2022: • BULLET -2023: ‣ TRIANGULAR BULLET -2024: ․ ONE DOT LEADER -2025: ‥ TWO DOT LEADER -2026: … HORIZONTAL ELLIPSIS -2027: ‧ HYPHENATION POINT -2028: 
 LINE SEPARATOR -2029: 
 PARAGRAPH SEPARATOR -202A: ‪ LEFT-TO-RIGHT EMBEDDING -202B: ‫ RIGHT-TO-LEFT EMBEDDING -202C: ‬ POP DIRECTIONAL FORMATTING -202D: ‭ LEFT-TO-RIGHT OVERRIDE -202E: ‮ RIGHT-TO-LEFT OVERRIDE -202F:   NARROW NO-BREAK SPACE -2030: ‰ PER MILLE SIGN -2031: ‱ PER TEN THOUSAND SIGN -2032: ′ PRIME -2033: ″ DOUBLE PRIME -2034: ‴ TRIPLE PRIME -2035: ‵ REVERSED PRIME -2036: ‶ REVERSED DOUBLE PRIME -2037: ‷ REVERSED TRIPLE PRIME -2038: ‸ CARET -2039: ‹ SINGLE LEFT-POINTING ANGLE QUOTATION MARK -203A: › SINGLE RIGHT-POINTING ANGLE QUOTATION MARK -203B: ※ REFERENCE MARK -203C: ‼ DOUBLE EXCLAMATION MARK -203D: ‽ INTERROBANG -203E: ‾ OVERLINE -203F: ‿ UNDERTIE -2040: ⁀ CHARACTER TIE -2041: ⁁ CARET INSERTION POINT -2042: ⁂ ASTERISM -2043: ⁃ HYPHEN BULLET -2044: ⁄ FRACTION SLASH -2045: ⁅ LEFT SQUARE BRACKET WITH QUILL -2046: ⁆ RIGHT SQUARE BRACKET WITH QUILL -2047: ⁇ DOUBLE QUESTION MARK -2048: ⁈ QUESTION EXCLAMATION MARK -2049: ⁉ EXCLAMATION QUESTION MARK -204A: ⁊ TIRONIAN SIGN ET -204B: ⁋ REVERSED PILCROW SIGN -204C: ⁌ BLACK LEFTWARDS BULLET -204D: ⁍ BLACK RIGHTWARDS BULLET -204E: ⁎ LOW ASTERISK -204F: ⁏ REVERSED SEMICOLON -2050: ⁐ CLOSE UP -2051: ⁑ TWO ASTERISKS ALIGNED VERTICALLY -2052: ⁒ COMMERCIAL MINUS SIGN -2053: ⁓ SWUNG DASH -2054: ⁔ INVERTED UNDERTIE -2055: ⁕ FLOWER PUNCTUATION MARK -2056: ⁖ THREE DOT PUNCTUATION -2057: ⁗ QUADRUPLE PRIME -2058: ⁘ FOUR DOT PUNCTUATION -2059: ⁙ FIVE DOT PUNCTUATION -205A: ⁚ TWO DOT PUNCTUATION -205B: ⁛ FOUR DOT MARK -205C: ⁜ DOTTED CROSS -205D: ⁝ TRICOLON -205E: ⁞ VERTICAL FOUR DOTS -205F:   MEDIUM MATHEMATICAL SPACE -2060: ⁠ WORD JOINER -2061: ⁡ FUNCTION APPLICATION -2062: ⁢ INVISIBLE TIMES -2063: ⁣ INVISIBLE SEPARATOR -2064: ⁤ INVISIBLE PLUS -206A:  INHIBIT SYMMETRIC SWAPPING -206B:  ACTIVATE SYMMETRIC SWAPPING -206C:  INHIBIT ARABIC FORM SHAPING -206D:  ACTIVATE ARABIC FORM SHAPING -206E:  NATIONAL DIGIT SHAPES -206F:  NOMINAL DIGIT SHAPES - -# Combining Diacritical Marks -0300: ̀ COMBINING GRAVE ACCENT -0301: ́ COMBINING ACUTE ACCENT -0302: ̂ COMBINING CIRCUMFLEX ACCENT -0303: ̃ COMBINING TILDE -0304: ̄ COMBINING MACRON -0305: ̅ COMBINING OVERLINE -0306: ̆ COMBINING BREVE -0307: ̇ COMBINING DOT ABOVE -0308: ̈ COMBINING DIAERESIS -0309: ̉ COMBINING HOOK ABOVE -030A: ̊ COMBINING RING ABOVE -030B: ̋ COMBINING DOUBLE ACUTE ACCENT -030C: ̌ COMBINING CARON -030D: ̍ COMBINING VERTICAL LINE ABOVE -030E: ̎ COMBINING DOUBLE VERTICAL LINE ABOVE -030F: ̏ COMBINING DOUBLE GRAVE ACCENT -0310: ̐ COMBINING CANDRABINDU -0311: ̑ COMBINING INVERTED BREVE -0312: ̒ COMBINING TURNED COMMA ABOVE -0313: ̓ COMBINING COMMA ABOVE -0314: ̔ COMBINING REVERSED COMMA ABOVE -0315: ̕ COMBINING COMMA ABOVE RIGHT -0316: ̖ COMBINING GRAVE ACCENT BELOW -0317: ̗ COMBINING ACUTE ACCENT BELOW -0318: ̘ COMBINING LEFT TACK BELOW -0319: ̙ COMBINING RIGHT TACK BELOW -031A: ̚ COMBINING LEFT ANGLE ABOVE -031B: ̛ COMBINING HORN -031C: ̜ COMBINING LEFT HALF RING BELOW -031D: ̝ COMBINING UP TACK BELOW -031E: ̞ COMBINING DOWN TACK BELOW -031F: ̟ COMBINING PLUS SIGN BELOW -0320: ̠ COMBINING MINUS SIGN BELOW -0321: ̡ COMBINING PALATALIZED HOOK BELOW -0322: ̢ COMBINING RETROFLEX HOOK BELOW -0323: ̣ COMBINING DOT BELOW -0324: ̤ COMBINING DIAERESIS BELOW -0325: ̥ COMBINING RING BELOW -0326: ̦ COMBINING COMMA BELOW -0327: ̧ COMBINING CEDILLA -0328: ̨ COMBINING OGONEK -0329: ̩ COMBINING VERTICAL LINE BELOW -032A: ̪ COMBINING BRIDGE BELOW -032B: ̫ COMBINING INVERTED DOUBLE ARCH BELOW -032C: ̬ COMBINING CARON BELOW -032D: ̭ COMBINING CIRCUMFLEX ACCENT BELOW -032E: ̮ COMBINING BREVE BELOW -032F: ̯ COMBINING INVERTED BREVE BELOW -0330: ̰ COMBINING TILDE BELOW -0331: ̱ COMBINING MACRON BELOW -0332: ̲ COMBINING LOW LINE -0333: ̳ COMBINING DOUBLE LOW LINE -0334: ̴ COMBINING TILDE OVERLAY -0335: ̵ COMBINING SHORT STROKE OVERLAY -0336: ̶ COMBINING LONG STROKE OVERLAY -0337: ̷ COMBINING SHORT SOLIDUS OVERLAY -0338: ̸ COMBINING LONG SOLIDUS OVERLAY -0339: ̹ COMBINING RIGHT HALF RING BELOW -033A: ̺ COMBINING INVERTED BRIDGE BELOW -033B: ̻ COMBINING SQUARE BELOW -033C: ̼ COMBINING SEAGULL BELOW -033D: ̽ COMBINING X ABOVE -033E: ̾ COMBINING VERTICAL TILDE -033F: ̿ COMBINING DOUBLE OVERLINE -0340: ̀ COMBINING GRAVE TONE MARK -0341: ́ COMBINING ACUTE TONE MARK -0342: ͂ COMBINING GREEK PERISPOMENI -0343: ̓ COMBINING GREEK KORONIS -0344: ̈́ COMBINING GREEK DIALYTIKA TONOS -0345: ͅ COMBINING GREEK YPOGEGRAMMENI -0346: ͆ COMBINING BRIDGE ABOVE -0347: ͇ COMBINING EQUALS SIGN BELOW -0348: ͈ COMBINING DOUBLE VERTICAL LINE BELOW -0349: ͉ COMBINING LEFT ANGLE BELOW -034A: ͊ COMBINING NOT TILDE ABOVE -034B: ͋ COMBINING HOMOTHETIC ABOVE -034C: ͌ COMBINING ALMOST EQUAL TO ABOVE -034D: ͍ COMBINING LEFT RIGHT ARROW BELOW -034E: ͎ COMBINING UPWARDS ARROW BELOW -034F: ͏ COMBINING GRAPHEME JOINER -0350: ͐ COMBINING RIGHT ARROWHEAD ABOVE -0351: ͑ COMBINING LEFT HALF RING ABOVE -0352: ͒ COMBINING FERMATA -0353: ͓ COMBINING X BELOW -0354: ͔ COMBINING LEFT ARROWHEAD BELOW -0355: ͕ COMBINING RIGHT ARROWHEAD BELOW -0356: ͖ COMBINING RIGHT ARROWHEAD AND UP ARROWHEAD BELOW -0357: ͗ COMBINING RIGHT HALF RING ABOVE -0358: ͘ COMBINING DOT ABOVE RIGHT -0359: ͙ COMBINING ASTERISK BELOW -035A: ͚ COMBINING DOUBLE RING BELOW -035B: ͛ COMBINING ZIGZAG ABOVE -035C: ͜ COMBINING DOUBLE BREVE BELOW -035D: ͝ COMBINING DOUBLE BREVE -035E: ͞ COMBINING DOUBLE MACRON -035F: ͟ COMBINING DOUBLE MACRON BELOW -0360: ͠ COMBINING DOUBLE TILDE -0361: ͡ COMBINING DOUBLE INVERTED BREVE -0362: ͢ COMBINING DOUBLE RIGHTWARDS ARROW BELOW -0363: ͣ COMBINING LATIN SMALL LETTER A -0364: ͤ COMBINING LATIN SMALL LETTER E -0365: ͥ COMBINING LATIN SMALL LETTER I -0366: ͦ COMBINING LATIN SMALL LETTER O -0367: ͧ COMBINING LATIN SMALL LETTER U -0368: ͨ COMBINING LATIN SMALL LETTER C -0369: ͩ COMBINING LATIN SMALL LETTER D -036A: ͪ COMBINING LATIN SMALL LETTER H -036B: ͫ COMBINING LATIN SMALL LETTER M -036C: ͬ COMBINING LATIN SMALL LETTER R -036D: ͭ COMBINING LATIN SMALL LETTER T -036E: ͮ COMBINING LATIN SMALL LETTER V -036F: ͯ COMBINING LATIN SMALL LETTER X - -# Mathematical Operators -2200: ∀ FOR ALL -2201: ∁ COMPLEMENT -2202: ∂ PARTIAL DIFFERENTIAL -2203: ∃ THERE EXISTS -2204: ∄ THERE DOES NOT EXIST -2205: ∅ EMPTY SET -2206: ∆ INCREMENT -2207: ∇ NABLA -2208: ∈ ELEMENT OF -2209: ∉ NOT AN ELEMENT OF -220A: ∊ SMALL ELEMENT OF -220B: ∋ CONTAINS AS MEMBER -220C: ∌ DOES NOT CONTAIN AS MEMBER -220D: ∍ SMALL CONTAINS AS MEMBER -220E: ∎ END OF PROOF -220F: ∏ N-ARY PRODUCT -2210: ∐ N-ARY COPRODUCT -2211: ∑ N-ARY SUMMATION -2212: − MINUS SIGN -2213: ∓ MINUS-OR-PLUS SIGN -2214: ∔ DOT PLUS -2215: ∕ DIVISION SLASH -2216: ∖ SET MINUS -2217: ∗ ASTERISK OPERATOR -2218: ∘ RING OPERATOR -2219: ∙ BULLET OPERATOR -221A: √ SQUARE ROOT -221B: ∛ CUBE ROOT -221C: ∜ FOURTH ROOT -221D: ∝ PROPORTIONAL TO -221E: ∞ INFINITY -221F: ∟ RIGHT ANGLE -2220: ∠ ANGLE -2221: ∡ MEASURED ANGLE -2222: ∢ SPHERICAL ANGLE -2223: ∣ DIVIDES -2224: ∤ DOES NOT DIVIDE -2225: ∥ PARALLEL TO -2226: ∦ NOT PARALLEL TO -2227: ∧ LOGICAL AND -2228: ∨ LOGICAL OR -2229: ∩ INTERSECTION -222A: ∪ UNION -222B: ∫ INTEGRAL -222C: ∬ DOUBLE INTEGRAL -222D: ∭ TRIPLE INTEGRAL -222E: ∮ CONTOUR INTEGRAL -222F: ∯ SURFACE INTEGRAL -2230: ∰ VOLUME INTEGRAL -2231: ∱ CLOCKWISE INTEGRAL -2232: ∲ CLOCKWISE CONTOUR INTEGRAL -2233: ∳ ANTICLOCKWISE CONTOUR INTEGRAL -2234: ∴ THEREFORE -2235: ∵ BECAUSE -2236: ∶ RATIO -2237: ∷ PROPORTION -2238: ∸ DOT MINUS -2239: ∹ EXCESS -223A: ∺ GEOMETRIC PROPORTION -223B: ∻ HOMOTHETIC -223C: ∼ TILDE OPERATOR -223D: ∽ REVERSED TILDE -223E: ∾ INVERTED LAZY S -223F: ∿ SINE WAVE -2240: ≀ WREATH PRODUCT -2241: ≁ NOT TILDE -2242: ≂ MINUS TILDE -2243: ≃ ASYMPTOTICALLY EQUAL TO -2244: ≄ NOT ASYMPTOTICALLY EQUAL TO -2245: ≅ APPROXIMATELY EQUAL TO -2246: ≆ APPROXIMATELY BUT NOT ACTUALLY EQUAL TO -2247: ≇ NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO -2248: ≈ ALMOST EQUAL TO -2249: ≉ NOT ALMOST EQUAL TO -224A: ≊ ALMOST EQUAL OR EQUAL TO -224B: ≋ TRIPLE TILDE -224C: ≌ ALL EQUAL TO -224D: ≍ EQUIVALENT TO -224E: ≎ GEOMETRICALLY EQUIVALENT TO -224F: ≏ DIFFERENCE BETWEEN -2250: ≐ APPROACHES THE LIMIT -2251: ≑ GEOMETRICALLY EQUAL TO -2252: ≒ APPROXIMATELY EQUAL TO OR THE IMAGE OF -2253: ≓ IMAGE OF OR APPROXIMATELY EQUAL TO -2254: ≔ COLON EQUALS -2255: ≕ EQUALS COLON -2256: ≖ RING IN EQUAL TO -2257: ≗ RING EQUAL TO -2258: ≘ CORRESPONDS TO -2259: ≙ ESTIMATES -225A: ≚ EQUIANGULAR TO -225B: ≛ STAR EQUALS -225C: ≜ DELTA EQUAL TO -225D: ≝ EQUAL TO BY DEFINITION -225E: ≞ MEASURED BY -225F: ≟ QUESTIONED EQUAL TO -2260: ≠ NOT EQUAL TO -2261: ≡ IDENTICAL TO -2262: ≢ NOT IDENTICAL TO -2263: ≣ STRICTLY EQUIVALENT TO -2264: ≤ LESS-THAN OR EQUAL TO -2265: ≥ GREATER-THAN OR EQUAL TO -2266: ≦ LESS-THAN OVER EQUAL TO -2267: ≧ GREATER-THAN OVER EQUAL TO -2268: ≨ LESS-THAN BUT NOT EQUAL TO -2269: ≩ GREATER-THAN BUT NOT EQUAL TO -226A: ≪ MUCH LESS-THAN -226B: ≫ MUCH GREATER-THAN -226C: ≬ BETWEEN -226D: ≭ NOT EQUIVALENT TO -226E: ≮ NOT LESS-THAN -226F: ≯ NOT GREATER-THAN -2270: ≰ NEITHER LESS-THAN NOR EQUAL TO -2271: ≱ NEITHER GREATER-THAN NOR EQUAL TO -2272: ≲ LESS-THAN OR EQUIVALENT TO -2273: ≳ GREATER-THAN OR EQUIVALENT TO -2274: ≴ NEITHER LESS-THAN NOR EQUIVALENT TO -2275: ≵ NEITHER GREATER-THAN NOR EQUIVALENT TO -2276: ≶ LESS-THAN OR GREATER-THAN -2277: ≷ GREATER-THAN OR LESS-THAN -2278: ≸ NEITHER LESS-THAN NOR GREATER-THAN -2279: ≹ NEITHER GREATER-THAN NOR LESS-THAN -227A: ≺ PRECEDES -227B: ≻ SUCCEEDS -227C: ≼ PRECEDES OR EQUAL TO -227D: ≽ SUCCEEDS OR EQUAL TO -227E: ≾ PRECEDES OR EQUIVALENT TO -227F: ≿ SUCCEEDS OR EQUIVALENT TO -2280: ⊀ DOES NOT PRECEDE -2281: ⊁ DOES NOT SUCCEED -2282: ⊂ SUBSET OF -2283: ⊃ SUPERSET OF -2284: ⊄ NOT A SUBSET OF -2285: ⊅ NOT A SUPERSET OF -2286: ⊆ SUBSET OF OR EQUAL TO -2287: ⊇ SUPERSET OF OR EQUAL TO -2288: ⊈ NEITHER A SUBSET OF NOR EQUAL TO -2289: ⊉ NEITHER A SUPERSET OF NOR EQUAL TO -228A: ⊊ SUBSET OF WITH NOT EQUAL TO -228B: ⊋ SUPERSET OF WITH NOT EQUAL TO -228C: ⊌ MULTISET -228D: ⊍ MULTISET MULTIPLICATION -228E: ⊎ MULTISET UNION -228F: ⊏ SQUARE IMAGE OF -2290: ⊐ SQUARE ORIGINAL OF -2291: ⊑ SQUARE IMAGE OF OR EQUAL TO -2292: ⊒ SQUARE ORIGINAL OF OR EQUAL TO -2293: ⊓ SQUARE CAP -2294: ⊔ SQUARE CUP -2295: ⊕ CIRCLED PLUS -2296: ⊖ CIRCLED MINUS -2297: ⊗ CIRCLED TIMES -2298: ⊘ CIRCLED DIVISION SLASH -2299: ⊙ CIRCLED DOT OPERATOR -229A: ⊚ CIRCLED RING OPERATOR -229B: ⊛ CIRCLED ASTERISK OPERATOR -229C: ⊜ CIRCLED EQUALS -229D: ⊝ CIRCLED DASH -229E: ⊞ SQUARED PLUS -229F: ⊟ SQUARED MINUS -22A0: ⊠ SQUARED TIMES -22A1: ⊡ SQUARED DOT OPERATOR -22A2: ⊢ RIGHT TACK -22A3: ⊣ LEFT TACK -22A4: ⊤ DOWN TACK -22A5: ⊥ UP TACK -22A6: ⊦ ASSERTION -22A7: ⊧ MODELS -22A8: ⊨ TRUE -22A9: ⊩ FORCES -22AA: ⊪ TRIPLE VERTICAL BAR RIGHT TURNSTILE -22AB: ⊫ DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE -22AC: ⊬ DOES NOT PROVE -22AD: ⊭ NOT TRUE -22AE: ⊮ DOES NOT FORCE -22AF: ⊯ NEGATED DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE -22B0: ⊰ PRECEDES UNDER RELATION -22B1: ⊱ SUCCEEDS UNDER RELATION -22B2: ⊲ NORMAL SUBGROUP OF -22B3: ⊳ CONTAINS AS NORMAL SUBGROUP -22B4: ⊴ NORMAL SUBGROUP OF OR EQUAL TO -22B5: ⊵ CONTAINS AS NORMAL SUBGROUP OR EQUAL TO -22B6: ⊶ ORIGINAL OF -22B7: ⊷ IMAGE OF -22B8: ⊸ MULTIMAP -22B9: ⊹ HERMITIAN CONJUGATE MATRIX -22BA: ⊺ INTERCALATE -22BB: ⊻ XOR -22BC: ⊼ NAND -22BD: ⊽ NOR -22BE: ⊾ RIGHT ANGLE WITH ARC -22BF: ⊿ RIGHT TRIANGLE -22C0: ⋀ N-ARY LOGICAL AND -22C1: ⋁ N-ARY LOGICAL OR -22C2: ⋂ N-ARY INTERSECTION -22C3: ⋃ N-ARY UNION -22C4: ⋄ DIAMOND OPERATOR -22C5: ⋅ DOT OPERATOR -22C6: ⋆ STAR OPERATOR -22C7: ⋇ DIVISION TIMES -22C8: ⋈ BOWTIE -22C9: ⋉ LEFT NORMAL FACTOR SEMIDIRECT PRODUCT -22CA: ⋊ RIGHT NORMAL FACTOR SEMIDIRECT PRODUCT -22CB: ⋋ LEFT SEMIDIRECT PRODUCT -22CC: ⋌ RIGHT SEMIDIRECT PRODUCT -22CD: ⋍ REVERSED TILDE EQUALS -22CE: ⋎ CURLY LOGICAL OR -22CF: ⋏ CURLY LOGICAL AND -22D0: ⋐ DOUBLE SUBSET -22D1: ⋑ DOUBLE SUPERSET -22D2: ⋒ DOUBLE INTERSECTION -22D3: ⋓ DOUBLE UNION -22D4: ⋔ PITCHFORK -22D5: ⋕ EQUAL AND PARALLEL TO -22D6: ⋖ LESS-THAN WITH DOT -22D7: ⋗ GREATER-THAN WITH DOT -22D8: ⋘ VERY MUCH LESS-THAN -22D9: ⋙ VERY MUCH GREATER-THAN -22DA: ⋚ LESS-THAN EQUAL TO OR GREATER-THAN -22DB: ⋛ GREATER-THAN EQUAL TO OR LESS-THAN -22DC: ⋜ EQUAL TO OR LESS-THAN -22DD: ⋝ EQUAL TO OR GREATER-THAN -22DE: ⋞ EQUAL TO OR PRECEDES -22DF: ⋟ EQUAL TO OR SUCCEEDS -22E0: ⋠ DOES NOT PRECEDE OR EQUAL -22E1: ⋡ DOES NOT SUCCEED OR EQUAL -22E2: ⋢ NOT SQUARE IMAGE OF OR EQUAL TO -22E3: ⋣ NOT SQUARE ORIGINAL OF OR EQUAL TO -22E4: ⋤ SQUARE IMAGE OF OR NOT EQUAL TO -22E5: ⋥ SQUARE ORIGINAL OF OR NOT EQUAL TO -22E6: ⋦ LESS-THAN BUT NOT EQUIVALENT TO -22E7: ⋧ GREATER-THAN BUT NOT EQUIVALENT TO -22E8: ⋨ PRECEDES BUT NOT EQUIVALENT TO -22E9: ⋩ SUCCEEDS BUT NOT EQUIVALENT TO -22EA: ⋪ NOT NORMAL SUBGROUP OF -22EB: ⋫ DOES NOT CONTAIN AS NORMAL SUBGROUP -22EC: ⋬ NOT NORMAL SUBGROUP OF OR EQUAL TO -22ED: ⋭ DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL -22EE: ⋮ VERTICAL ELLIPSIS -22EF: ⋯ MIDLINE HORIZONTAL ELLIPSIS -22F0: ⋰ UP RIGHT DIAGONAL ELLIPSIS -22F1: ⋱ DOWN RIGHT DIAGONAL ELLIPSIS -22F2: ⋲ ELEMENT OF WITH LONG HORIZONTAL STROKE -22F3: ⋳ ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE -22F4: ⋴ SMALL ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE -22F5: ⋵ ELEMENT OF WITH DOT ABOVE -22F6: ⋶ ELEMENT OF WITH OVERBAR -22F7: ⋷ SMALL ELEMENT OF WITH OVERBAR -22F8: ⋸ ELEMENT OF WITH UNDERBAR -22F9: ⋹ ELEMENT OF WITH TWO HORIZONTAL STROKES -22FA: ⋺ CONTAINS WITH LONG HORIZONTAL STROKE -22FB: ⋻ CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE -22FC: ⋼ SMALL CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE -22FD: ⋽ CONTAINS WITH OVERBAR -22FE: ⋾ SMALL CONTAINS WITH OVERBAR -22FF: ⋿ Z NOTATION BAG MEMBERSHIP - -FE33: ︳ PRESENTATION FORM FOR VERTICAL LOW LINE -FFFD: � REPLACEMENT CHARACTER +# Basic Latin +0020: SPACE +0021: ! EXCLAMATION MARK +0022: " QUOTATION MARK +0023: # NUMBER SIGN +0024: $ DOLLAR SIGN +0025: % PERCENT SIGN +0026: & AMPERSAND +0027: ' APOSTROPHE +0028: ( LEFT PARENTHESIS +0029: ) RIGHT PARENTHESIS +002A: * ASTERISK +002B: + PLUS SIGN +002C: , COMMA +002D: - HYPHEN-MINUS +002E: . FULL STOP +002F: / SOLIDUS +0030: 0 DIGIT ZERO +0031: 1 DIGIT ONE +0032: 2 DIGIT TWO +0033: 3 DIGIT THREE +0034: 4 DIGIT FOUR +0035: 5 DIGIT FIVE +0036: 6 DIGIT SIX +0037: 7 DIGIT SEVEN +0038: 8 DIGIT EIGHT +0039: 9 DIGIT NINE +003A: : COLON +003B: ; SEMICOLON +003C: < LESS-THAN SIGN +003D: = EQUALS SIGN +003E: > GREATER-THAN SIGN +003F: ? QUESTION MARK +0040: @ COMMERCIAL AT +0041: A LATIN CAPITAL LETTER A +0042: B LATIN CAPITAL LETTER B +0043: C LATIN CAPITAL LETTER C +0044: D LATIN CAPITAL LETTER D +0045: E LATIN CAPITAL LETTER E +0046: F LATIN CAPITAL LETTER F +0047: G LATIN CAPITAL LETTER G +0048: H LATIN CAPITAL LETTER H +0049: I LATIN CAPITAL LETTER I +004A: J LATIN CAPITAL LETTER J +004B: K LATIN CAPITAL LETTER K +004C: L LATIN CAPITAL LETTER L +004D: M LATIN CAPITAL LETTER M +004E: N LATIN CAPITAL LETTER N +004F: O LATIN CAPITAL LETTER O +0050: P LATIN CAPITAL LETTER P +0051: Q LATIN CAPITAL LETTER Q +0052: R LATIN CAPITAL LETTER R +0053: S LATIN CAPITAL LETTER S +0054: T LATIN CAPITAL LETTER T +0055: U LATIN CAPITAL LETTER U +0056: V LATIN CAPITAL LETTER V +0057: W LATIN CAPITAL LETTER W +0058: X LATIN CAPITAL LETTER X +0059: Y LATIN CAPITAL LETTER Y +005A: Z LATIN CAPITAL LETTER Z +005B: [ LEFT SQUARE BRACKET +005C: \ REVERSE SOLIDUS +005D: ] RIGHT SQUARE BRACKET +005E: ^ CIRCUMFLEX ACCENT +005F: _ LOW LINE +0060: ` GRAVE ACCENT +0061: a LATIN SMALL LETTER A +0062: b LATIN SMALL LETTER B +0063: c LATIN SMALL LETTER C +0064: d LATIN SMALL LETTER D +0065: e LATIN SMALL LETTER E +0066: f LATIN SMALL LETTER F +0067: g LATIN SMALL LETTER G +0068: h LATIN SMALL LETTER H +0069: i LATIN SMALL LETTER I +006A: j LATIN SMALL LETTER J +006B: k LATIN SMALL LETTER K +006C: l LATIN SMALL LETTER L +006D: m LATIN SMALL LETTER M +006E: n LATIN SMALL LETTER N +006F: o LATIN SMALL LETTER O +0070: p LATIN SMALL LETTER P +0071: q LATIN SMALL LETTER Q +0072: r LATIN SMALL LETTER R +0073: s LATIN SMALL LETTER S +0074: t LATIN SMALL LETTER T +0075: u LATIN SMALL LETTER U +0076: v LATIN SMALL LETTER V +0077: w LATIN SMALL LETTER W +0078: x LATIN SMALL LETTER X +0079: y LATIN SMALL LETTER Y +007A: z LATIN SMALL LETTER Z +007B: { LEFT CURLY BRACKET +007C: | VERTICAL LINE +007D: } RIGHT CURLY BRACKET +007E: ~ TILDE + +# Latin-1 Supplement +00A0:   NO-BREAK SPACE +00A1: ¡ INVERTED EXCLAMATION MARK +00A2: ¢ CENT SIGN +00A3: £ POUND SIGN +00A4: ¤ CURRENCY SIGN +00A5: ¥ YEN SIGN +00A6: ¦ BROKEN BAR +00A7: § SECTION SIGN +00A8: ¨ DIAERESIS +00A9: © COPYRIGHT SIGN +00AA: ª FEMININE ORDINAL INDICATOR +00AB: « LEFT-POINTING DOUBLE ANGLE QUOTATION MARK +00AC: ¬ NOT SIGN +00AD: ­ SOFT HYPHEN +00AE: ® REGISTERED SIGN +00AF: ¯ MACRON +00B0: ° DEGREE SIGN +00B1: ± PLUS-MINUS SIGN +00B2: ² SUPERSCRIPT TWO +00B3: ³ SUPERSCRIPT THREE +00B4: ´ ACUTE ACCENT +00B5: µ MICRO SIGN +00B6: ¶ PILCROW SIGN +00B7: · MIDDLE DOT +00B8: ¸ CEDILLA +00B9: ¹ SUPERSCRIPT ONE +00BA: º MASCULINE ORDINAL INDICATOR +00BB: » RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK +00BC: ¼ VULGAR FRACTION ONE QUARTER +00BD: ½ VULGAR FRACTION ONE HALF +00BE: ¾ VULGAR FRACTION THREE QUARTERS +00BF: ¿ INVERTED QUESTION MARK +00C0: À LATIN CAPITAL LETTER A WITH GRAVE +00C1: Á LATIN CAPITAL LETTER A WITH ACUTE +00C2:  LATIN CAPITAL LETTER A WITH CIRCUMFLEX +00C3: à LATIN CAPITAL LETTER A WITH TILDE +00C4: Ä LATIN CAPITAL LETTER A WITH DIAERESIS +00C5: Å LATIN CAPITAL LETTER A WITH RING ABOVE +00C6: Æ LATIN CAPITAL LETTER AE +00C7: Ç LATIN CAPITAL LETTER C WITH CEDILLA +00C8: È LATIN CAPITAL LETTER E WITH GRAVE +00C9: É LATIN CAPITAL LETTER E WITH ACUTE +00CA: Ê LATIN CAPITAL LETTER E WITH CIRCUMFLEX +00CB: Ë LATIN CAPITAL LETTER E WITH DIAERESIS +00CC: Ì LATIN CAPITAL LETTER I WITH GRAVE +00CD: Í LATIN CAPITAL LETTER I WITH ACUTE +00CE: Î LATIN CAPITAL LETTER I WITH CIRCUMFLEX +00CF: Ï LATIN CAPITAL LETTER I WITH DIAERESIS +00D0: Ð LATIN CAPITAL LETTER ETH +00D1: Ñ LATIN CAPITAL LETTER N WITH TILDE +00D2: Ò LATIN CAPITAL LETTER O WITH GRAVE +00D3: Ó LATIN CAPITAL LETTER O WITH ACUTE +00D4: Ô LATIN CAPITAL LETTER O WITH CIRCUMFLEX +00D5: Õ LATIN CAPITAL LETTER O WITH TILDE +00D6: Ö LATIN CAPITAL LETTER O WITH DIAERESIS +00D7: × MULTIPLICATION SIGN +00D8: Ø LATIN CAPITAL LETTER O WITH STROKE +00D9: Ù LATIN CAPITAL LETTER U WITH GRAVE +00DA: Ú LATIN CAPITAL LETTER U WITH ACUTE +00DB: Û LATIN CAPITAL LETTER U WITH CIRCUMFLEX +00DC: Ü LATIN CAPITAL LETTER U WITH DIAERESIS +00DD: Ý LATIN CAPITAL LETTER Y WITH ACUTE +00DE: Þ LATIN CAPITAL LETTER THORN +00DF: ß LATIN SMALL LETTER SHARP S +00E0: à LATIN SMALL LETTER A WITH GRAVE +00E1: á LATIN SMALL LETTER A WITH ACUTE +00E2: â LATIN SMALL LETTER A WITH CIRCUMFLEX +00E3: ã LATIN SMALL LETTER A WITH TILDE +00E4: ä LATIN SMALL LETTER A WITH DIAERESIS +00E5: å LATIN SMALL LETTER A WITH RING ABOVE +00E6: æ LATIN SMALL LETTER AE +00E7: ç LATIN SMALL LETTER C WITH CEDILLA +00E8: è LATIN SMALL LETTER E WITH GRAVE +00E9: é LATIN SMALL LETTER E WITH ACUTE +00EA: ê LATIN SMALL LETTER E WITH CIRCUMFLEX +00EB: ë LATIN SMALL LETTER E WITH DIAERESIS +00EC: ì LATIN SMALL LETTER I WITH GRAVE +00ED: í LATIN SMALL LETTER I WITH ACUTE +00EE: î LATIN SMALL LETTER I WITH CIRCUMFLEX +00EF: ï LATIN SMALL LETTER I WITH DIAERESIS +00F0: ð LATIN SMALL LETTER ETH +00F1: ñ LATIN SMALL LETTER N WITH TILDE +00F2: ò LATIN SMALL LETTER O WITH GRAVE +00F3: ó LATIN SMALL LETTER O WITH ACUTE +00F4: ô LATIN SMALL LETTER O WITH CIRCUMFLEX +00F5: õ LATIN SMALL LETTER O WITH TILDE +00F6: ö LATIN SMALL LETTER O WITH DIAERESIS +00F7: ÷ DIVISION SIGN +00F8: ø LATIN SMALL LETTER O WITH STROKE +00F9: ù LATIN SMALL LETTER U WITH GRAVE +00FA: ú LATIN SMALL LETTER U WITH ACUTE +00FB: û LATIN SMALL LETTER U WITH CIRCUMFLEX +00FC: ü LATIN SMALL LETTER U WITH DIAERESIS +00FD: ý LATIN SMALL LETTER Y WITH ACUTE +00FE: þ LATIN SMALL LETTER THORN +00FF: ÿ LATIN SMALL LETTER Y WITH DIAERESIS + +# Latin Extended-A +0100: Ā LATIN CAPITAL LETTER A WITH MACRON +0101: ā LATIN SMALL LETTER A WITH MACRON +0102: Ă LATIN CAPITAL LETTER A WITH BREVE +0103: ă LATIN SMALL LETTER A WITH BREVE +0104: Ą LATIN CAPITAL LETTER A WITH OGONEK +0105: ą LATIN SMALL LETTER A WITH OGONEK +0106: Ć LATIN CAPITAL LETTER C WITH ACUTE +0107: ć LATIN SMALL LETTER C WITH ACUTE +0108: Ĉ LATIN CAPITAL LETTER C WITH CIRCUMFLEX +0109: ĉ LATIN SMALL LETTER C WITH CIRCUMFLEX +010A: Ċ LATIN CAPITAL LETTER C WITH DOT ABOVE +010B: ċ LATIN SMALL LETTER C WITH DOT ABOVE +010C: Č LATIN CAPITAL LETTER C WITH CARON +010D: č LATIN SMALL LETTER C WITH CARON +010E: Ď LATIN CAPITAL LETTER D WITH CARON +010F: ď LATIN SMALL LETTER D WITH CARON +0110: Đ LATIN CAPITAL LETTER D WITH STROKE +0111: đ LATIN SMALL LETTER D WITH STROKE +0112: Ē LATIN CAPITAL LETTER E WITH MACRON +0113: ē LATIN SMALL LETTER E WITH MACRON +0114: Ĕ LATIN CAPITAL LETTER E WITH BREVE +0115: ĕ LATIN SMALL LETTER E WITH BREVE +0116: Ė LATIN CAPITAL LETTER E WITH DOT ABOVE +0117: ė LATIN SMALL LETTER E WITH DOT ABOVE +0118: Ę LATIN CAPITAL LETTER E WITH OGONEK +0119: ę LATIN SMALL LETTER E WITH OGONEK +011A: Ě LATIN CAPITAL LETTER E WITH CARON +011B: ě LATIN SMALL LETTER E WITH CARON +011C: Ĝ LATIN CAPITAL LETTER G WITH CIRCUMFLEX +011D: ĝ LATIN SMALL LETTER G WITH CIRCUMFLEX +011E: Ğ LATIN CAPITAL LETTER G WITH BREVE +011F: ğ LATIN SMALL LETTER G WITH BREVE +0120: Ġ LATIN CAPITAL LETTER G WITH DOT ABOVE +0121: ġ LATIN SMALL LETTER G WITH DOT ABOVE +0122: Ģ LATIN CAPITAL LETTER G WITH CEDILLA +0123: ģ LATIN SMALL LETTER G WITH CEDILLA +0124: Ĥ LATIN CAPITAL LETTER H WITH CIRCUMFLEX +0125: ĥ LATIN SMALL LETTER H WITH CIRCUMFLEX +0126: Ħ LATIN CAPITAL LETTER H WITH STROKE +0127: ħ LATIN SMALL LETTER H WITH STROKE +0128: Ĩ LATIN CAPITAL LETTER I WITH TILDE +0129: ĩ LATIN SMALL LETTER I WITH TILDE +012A: Ī LATIN CAPITAL LETTER I WITH MACRON +012B: ī LATIN SMALL LETTER I WITH MACRON +012C: Ĭ LATIN CAPITAL LETTER I WITH BREVE +012D: ĭ LATIN SMALL LETTER I WITH BREVE +012E: Į LATIN CAPITAL LETTER I WITH OGONEK +012F: į LATIN SMALL LETTER I WITH OGONEK +0130: İ LATIN CAPITAL LETTER I WITH DOT ABOVE +0131: ı LATIN SMALL LETTER DOTLESS I +0132: IJ LATIN CAPITAL LIGATURE IJ +0133: ij LATIN SMALL LIGATURE IJ +0134: Ĵ LATIN CAPITAL LETTER J WITH CIRCUMFLEX +0135: ĵ LATIN SMALL LETTER J WITH CIRCUMFLEX +0136: Ķ LATIN CAPITAL LETTER K WITH CEDILLA +0137: ķ LATIN SMALL LETTER K WITH CEDILLA +0138: ĸ LATIN SMALL LETTER KRA +0139: Ĺ LATIN CAPITAL LETTER L WITH ACUTE +013A: ĺ LATIN SMALL LETTER L WITH ACUTE +013B: Ļ LATIN CAPITAL LETTER L WITH CEDILLA +013C: ļ LATIN SMALL LETTER L WITH CEDILLA +013D: Ľ LATIN CAPITAL LETTER L WITH CARON +013E: ľ LATIN SMALL LETTER L WITH CARON +013F: Ŀ LATIN CAPITAL LETTER L WITH MIDDLE DOT +0140: ŀ LATIN SMALL LETTER L WITH MIDDLE DOT +0141: Ł LATIN CAPITAL LETTER L WITH STROKE +0142: ł LATIN SMALL LETTER L WITH STROKE +0143: Ń LATIN CAPITAL LETTER N WITH ACUTE +0144: ń LATIN SMALL LETTER N WITH ACUTE +0145: Ņ LATIN CAPITAL LETTER N WITH CEDILLA +0146: ņ LATIN SMALL LETTER N WITH CEDILLA +0147: Ň LATIN CAPITAL LETTER N WITH CARON +0148: ň LATIN SMALL LETTER N WITH CARON +0149: ʼn LATIN SMALL LETTER N PRECEDED BY APOSTROPHE +014A: Ŋ LATIN CAPITAL LETTER ENG +014B: ŋ LATIN SMALL LETTER ENG +014C: Ō LATIN CAPITAL LETTER O WITH MACRON +014D: ō LATIN SMALL LETTER O WITH MACRON +014E: Ŏ LATIN CAPITAL LETTER O WITH BREVE +014F: ŏ LATIN SMALL LETTER O WITH BREVE +0150: Ő LATIN CAPITAL LETTER O WITH DOUBLE ACUTE +0151: ő LATIN SMALL LETTER O WITH DOUBLE ACUTE +0152: Œ LATIN CAPITAL LIGATURE OE +0153: œ LATIN SMALL LIGATURE OE +0154: Ŕ LATIN CAPITAL LETTER R WITH ACUTE +0155: ŕ LATIN SMALL LETTER R WITH ACUTE +0156: Ŗ LATIN CAPITAL LETTER R WITH CEDILLA +0157: ŗ LATIN SMALL LETTER R WITH CEDILLA +0158: Ř LATIN CAPITAL LETTER R WITH CARON +0159: ř LATIN SMALL LETTER R WITH CARON +015A: Ś LATIN CAPITAL LETTER S WITH ACUTE +015B: ś LATIN SMALL LETTER S WITH ACUTE +015C: Ŝ LATIN CAPITAL LETTER S WITH CIRCUMFLEX +015D: ŝ LATIN SMALL LETTER S WITH CIRCUMFLEX +015E: Ş LATIN CAPITAL LETTER S WITH CEDILLA +015F: ş LATIN SMALL LETTER S WITH CEDILLA +0160: Š LATIN CAPITAL LETTER S WITH CARON +0161: š LATIN SMALL LETTER S WITH CARON +0162: Ţ LATIN CAPITAL LETTER T WITH CEDILLA +0163: ţ LATIN SMALL LETTER T WITH CEDILLA +0164: Ť LATIN CAPITAL LETTER T WITH CARON +0165: ť LATIN SMALL LETTER T WITH CARON +0166: Ŧ LATIN CAPITAL LETTER T WITH STROKE +0167: ŧ LATIN SMALL LETTER T WITH STROKE +0168: Ũ LATIN CAPITAL LETTER U WITH TILDE +0169: ũ LATIN SMALL LETTER U WITH TILDE +016A: Ū LATIN CAPITAL LETTER U WITH MACRON +016B: ū LATIN SMALL LETTER U WITH MACRON +016C: Ŭ LATIN CAPITAL LETTER U WITH BREVE +016D: ŭ LATIN SMALL LETTER U WITH BREVE +016E: Ů LATIN CAPITAL LETTER U WITH RING ABOVE +016F: ů LATIN SMALL LETTER U WITH RING ABOVE +0170: Ű LATIN CAPITAL LETTER U WITH DOUBLE ACUTE +0171: ű LATIN SMALL LETTER U WITH DOUBLE ACUTE +0172: Ų LATIN CAPITAL LETTER U WITH OGONEK +0173: ų LATIN SMALL LETTER U WITH OGONEK +0174: Ŵ LATIN CAPITAL LETTER W WITH CIRCUMFLEX +0175: ŵ LATIN SMALL LETTER W WITH CIRCUMFLEX +0176: Ŷ LATIN CAPITAL LETTER Y WITH CIRCUMFLEX +0177: ŷ LATIN SMALL LETTER Y WITH CIRCUMFLEX +0178: Ÿ LATIN CAPITAL LETTER Y WITH DIAERESIS +0179: Ź LATIN CAPITAL LETTER Z WITH ACUTE +017A: ź LATIN SMALL LETTER Z WITH ACUTE +017B: Ż LATIN CAPITAL LETTER Z WITH DOT ABOVE +017C: ż LATIN SMALL LETTER Z WITH DOT ABOVE +017D: Ž LATIN CAPITAL LETTER Z WITH CARON +017E: ž LATIN SMALL LETTER Z WITH CARON +017F: ſ LATIN SMALL LETTER LONG S + +# Latin Extended-B +0180: ƀ LATIN SMALL LETTER B WITH STROKE +0181: Ɓ LATIN CAPITAL LETTER B WITH HOOK +0182: Ƃ LATIN CAPITAL LETTER B WITH TOPBAR +0183: ƃ LATIN SMALL LETTER B WITH TOPBAR +0184: Ƅ LATIN CAPITAL LETTER TONE SIX +0185: ƅ LATIN SMALL LETTER TONE SIX +0186: Ɔ LATIN CAPITAL LETTER OPEN O +0187: Ƈ LATIN CAPITAL LETTER C WITH HOOK +0188: ƈ LATIN SMALL LETTER C WITH HOOK +0189: Ɖ LATIN CAPITAL LETTER AFRICAN D +018A: Ɗ LATIN CAPITAL LETTER D WITH HOOK +018B: Ƌ LATIN CAPITAL LETTER D WITH TOPBAR +018C: ƌ LATIN SMALL LETTER D WITH TOPBAR +018D: ƍ LATIN SMALL LETTER TURNED DELTA +018E: Ǝ LATIN CAPITAL LETTER REVERSED E +018F: Ə LATIN CAPITAL LETTER SCHWA +0190: Ɛ LATIN CAPITAL LETTER OPEN E +0191: Ƒ LATIN CAPITAL LETTER F WITH HOOK +0192: ƒ LATIN SMALL LETTER F WITH HOOK +0193: Ɠ LATIN CAPITAL LETTER G WITH HOOK +0194: Ɣ LATIN CAPITAL LETTER GAMMA +0195: ƕ LATIN SMALL LETTER HV +0196: Ɩ LATIN CAPITAL LETTER IOTA +0197: Ɨ LATIN CAPITAL LETTER I WITH STROKE +0198: Ƙ LATIN CAPITAL LETTER K WITH HOOK +0199: ƙ LATIN SMALL LETTER K WITH HOOK +019A: ƚ LATIN SMALL LETTER L WITH BAR +019B: ƛ LATIN SMALL LETTER LAMBDA WITH STROKE +019C: Ɯ LATIN CAPITAL LETTER TURNED M +019D: Ɲ LATIN CAPITAL LETTER N WITH LEFT HOOK +019E: ƞ LATIN SMALL LETTER N WITH LONG RIGHT LEG +019F: Ɵ LATIN CAPITAL LETTER O WITH MIDDLE TILDE +01A0: Ơ LATIN CAPITAL LETTER O WITH HORN +01A1: ơ LATIN SMALL LETTER O WITH HORN +01A2: Ƣ LATIN CAPITAL LETTER OI +01A3: ƣ LATIN SMALL LETTER OI +01A4: Ƥ LATIN CAPITAL LETTER P WITH HOOK +01A5: ƥ LATIN SMALL LETTER P WITH HOOK +01A6: Ʀ LATIN LETTER YR +01A7: Ƨ LATIN CAPITAL LETTER TONE TWO +01A8: ƨ LATIN SMALL LETTER TONE TWO +01A9: Ʃ LATIN CAPITAL LETTER ESH +01AA: ƪ LATIN LETTER REVERSED ESH LOOP +01AB: ƫ LATIN SMALL LETTER T WITH PALATAL HOOK +01AC: Ƭ LATIN CAPITAL LETTER T WITH HOOK +01AD: ƭ LATIN SMALL LETTER T WITH HOOK +01AE: Ʈ LATIN CAPITAL LETTER T WITH RETROFLEX HOOK +01AF: Ư LATIN CAPITAL LETTER U WITH HORN +01B0: ư LATIN SMALL LETTER U WITH HORN +01B1: Ʊ LATIN CAPITAL LETTER UPSILON +01B2: Ʋ LATIN CAPITAL LETTER V WITH HOOK +01B3: Ƴ LATIN CAPITAL LETTER Y WITH HOOK +01B4: ƴ LATIN SMALL LETTER Y WITH HOOK +01B5: Ƶ LATIN CAPITAL LETTER Z WITH STROKE +01B6: ƶ LATIN SMALL LETTER Z WITH STROKE +01B7: Ʒ LATIN CAPITAL LETTER EZH +01B8: Ƹ LATIN CAPITAL LETTER EZH REVERSED +01B9: ƹ LATIN SMALL LETTER EZH REVERSED +01BA: ƺ LATIN SMALL LETTER EZH WITH TAIL +01BB: ƻ LATIN LETTER TWO WITH STROKE +01BC: Ƽ LATIN CAPITAL LETTER TONE FIVE +01BD: ƽ LATIN SMALL LETTER TONE FIVE +01BE: ƾ LATIN LETTER INVERTED GLOTTAL STOP WITH STROKE +01BF: ƿ LATIN LETTER WYNN +01C0: ǀ LATIN LETTER DENTAL CLICK +01C1: ǁ LATIN LETTER LATERAL CLICK +01C2: ǂ LATIN LETTER ALVEOLAR CLICK +01C3: ǃ LATIN LETTER RETROFLEX CLICK +01C4: DŽ LATIN CAPITAL LETTER DZ WITH CARON +01C5: Dž LATIN CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON +01C6: dž LATIN SMALL LETTER DZ WITH CARON +01C7: LJ LATIN CAPITAL LETTER LJ +01C8: Lj LATIN CAPITAL LETTER L WITH SMALL LETTER J +01C9: lj LATIN SMALL LETTER LJ +01CA: NJ LATIN CAPITAL LETTER NJ +01CB: Nj LATIN CAPITAL LETTER N WITH SMALL LETTER J +01CC: nj LATIN SMALL LETTER NJ +01CD: Ǎ LATIN CAPITAL LETTER A WITH CARON +01CE: ǎ LATIN SMALL LETTER A WITH CARON +01CF: Ǐ LATIN CAPITAL LETTER I WITH CARON +01D0: ǐ LATIN SMALL LETTER I WITH CARON +01D1: Ǒ LATIN CAPITAL LETTER O WITH CARON +01D2: ǒ LATIN SMALL LETTER O WITH CARON +01D3: Ǔ LATIN CAPITAL LETTER U WITH CARON +01D4: ǔ LATIN SMALL LETTER U WITH CARON +01D5: Ǖ LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON +01D6: ǖ LATIN SMALL LETTER U WITH DIAERESIS AND MACRON +01D7: Ǘ LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE +01D8: ǘ LATIN SMALL LETTER U WITH DIAERESIS AND ACUTE +01D9: Ǚ LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON +01DA: ǚ LATIN SMALL LETTER U WITH DIAERESIS AND CARON +01DB: Ǜ LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE +01DC: ǜ LATIN SMALL LETTER U WITH DIAERESIS AND GRAVE +01DD: ǝ LATIN SMALL LETTER TURNED E +01DE: Ǟ LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON +01DF: ǟ LATIN SMALL LETTER A WITH DIAERESIS AND MACRON +01E0: Ǡ LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON +01E1: ǡ LATIN SMALL LETTER A WITH DOT ABOVE AND MACRON +01E2: Ǣ LATIN CAPITAL LETTER AE WITH MACRON +01E3: ǣ LATIN SMALL LETTER AE WITH MACRON +01E4: Ǥ LATIN CAPITAL LETTER G WITH STROKE +01E5: ǥ LATIN SMALL LETTER G WITH STROKE +01E6: Ǧ LATIN CAPITAL LETTER G WITH CARON +01E7: ǧ LATIN SMALL LETTER G WITH CARON +01E8: Ǩ LATIN CAPITAL LETTER K WITH CARON +01E9: ǩ LATIN SMALL LETTER K WITH CARON +01EA: Ǫ LATIN CAPITAL LETTER O WITH OGONEK +01EB: ǫ LATIN SMALL LETTER O WITH OGONEK +01EC: Ǭ LATIN CAPITAL LETTER O WITH OGONEK AND MACRON +01ED: ǭ LATIN SMALL LETTER O WITH OGONEK AND MACRON +01EE: Ǯ LATIN CAPITAL LETTER EZH WITH CARON +01EF: ǯ LATIN SMALL LETTER EZH WITH CARON +01F0: ǰ LATIN SMALL LETTER J WITH CARON +01F1: DZ LATIN CAPITAL LETTER DZ +01F2: Dz LATIN CAPITAL LETTER D WITH SMALL LETTER Z +01F3: dz LATIN SMALL LETTER DZ +01F4: Ǵ LATIN CAPITAL LETTER G WITH ACUTE +01F5: ǵ LATIN SMALL LETTER G WITH ACUTE +01F6: Ƕ LATIN CAPITAL LETTER HWAIR +01F7: Ƿ LATIN CAPITAL LETTER WYNN +01F8: Ǹ LATIN CAPITAL LETTER N WITH GRAVE +01F9: ǹ LATIN SMALL LETTER N WITH GRAVE +01FA: Ǻ LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE +01FB: ǻ LATIN SMALL LETTER A WITH RING ABOVE AND ACUTE +01FC: Ǽ LATIN CAPITAL LETTER AE WITH ACUTE +01FD: ǽ LATIN SMALL LETTER AE WITH ACUTE +01FE: Ǿ LATIN CAPITAL LETTER O WITH STROKE AND ACUTE +01FF: ǿ LATIN SMALL LETTER O WITH STROKE AND ACUTE +0200: Ȁ LATIN CAPITAL LETTER A WITH DOUBLE GRAVE +0201: ȁ LATIN SMALL LETTER A WITH DOUBLE GRAVE +0202: Ȃ LATIN CAPITAL LETTER A WITH INVERTED BREVE +0203: ȃ LATIN SMALL LETTER A WITH INVERTED BREVE +0204: Ȅ LATIN CAPITAL LETTER E WITH DOUBLE GRAVE +0205: ȅ LATIN SMALL LETTER E WITH DOUBLE GRAVE +0206: Ȇ LATIN CAPITAL LETTER E WITH INVERTED BREVE +0207: ȇ LATIN SMALL LETTER E WITH INVERTED BREVE +0208: Ȉ LATIN CAPITAL LETTER I WITH DOUBLE GRAVE +0209: ȉ LATIN SMALL LETTER I WITH DOUBLE GRAVE +020A: Ȋ LATIN CAPITAL LETTER I WITH INVERTED BREVE +020B: ȋ LATIN SMALL LETTER I WITH INVERTED BREVE +020C: Ȍ LATIN CAPITAL LETTER O WITH DOUBLE GRAVE +020D: ȍ LATIN SMALL LETTER O WITH DOUBLE GRAVE +020E: Ȏ LATIN CAPITAL LETTER O WITH INVERTED BREVE +020F: ȏ LATIN SMALL LETTER O WITH INVERTED BREVE +0210: Ȑ LATIN CAPITAL LETTER R WITH DOUBLE GRAVE +0211: ȑ LATIN SMALL LETTER R WITH DOUBLE GRAVE +0212: Ȓ LATIN CAPITAL LETTER R WITH INVERTED BREVE +0213: ȓ LATIN SMALL LETTER R WITH INVERTED BREVE +0214: Ȕ LATIN CAPITAL LETTER U WITH DOUBLE GRAVE +0215: ȕ LATIN SMALL LETTER U WITH DOUBLE GRAVE +0216: Ȗ LATIN CAPITAL LETTER U WITH INVERTED BREVE +0217: ȗ LATIN SMALL LETTER U WITH INVERTED BREVE +0218: Ș LATIN CAPITAL LETTER S WITH COMMA BELOW +0219: ș LATIN SMALL LETTER S WITH COMMA BELOW +021A: Ț LATIN CAPITAL LETTER T WITH COMMA BELOW +021B: ț LATIN SMALL LETTER T WITH COMMA BELOW +021C: Ȝ LATIN CAPITAL LETTER YOGH +021D: ȝ LATIN SMALL LETTER YOGH +021E: Ȟ LATIN CAPITAL LETTER H WITH CARON +021F: ȟ LATIN SMALL LETTER H WITH CARON +0220: Ƞ LATIN CAPITAL LETTER N WITH LONG RIGHT LEG +0221: ȡ LATIN SMALL LETTER D WITH CURL +0222: Ȣ LATIN CAPITAL LETTER OU +0223: ȣ LATIN SMALL LETTER OU +0224: Ȥ LATIN CAPITAL LETTER Z WITH HOOK +0225: ȥ LATIN SMALL LETTER Z WITH HOOK +0226: Ȧ LATIN CAPITAL LETTER A WITH DOT ABOVE +0227: ȧ LATIN SMALL LETTER A WITH DOT ABOVE +0228: Ȩ LATIN CAPITAL LETTER E WITH CEDILLA +0229: ȩ LATIN SMALL LETTER E WITH CEDILLA +022A: Ȫ LATIN CAPITAL LETTER O WITH DIAERESIS AND MACRON +022B: ȫ LATIN SMALL LETTER O WITH DIAERESIS AND MACRON +022C: Ȭ LATIN CAPITAL LETTER O WITH TILDE AND MACRON +022D: ȭ LATIN SMALL LETTER O WITH TILDE AND MACRON +022E: Ȯ LATIN CAPITAL LETTER O WITH DOT ABOVE +022F: ȯ LATIN SMALL LETTER O WITH DOT ABOVE +0230: Ȱ LATIN CAPITAL LETTER O WITH DOT ABOVE AND MACRON +0231: ȱ LATIN SMALL LETTER O WITH DOT ABOVE AND MACRON +0232: Ȳ LATIN CAPITAL LETTER Y WITH MACRON +0233: ȳ LATIN SMALL LETTER Y WITH MACRON +0234: ȴ LATIN SMALL LETTER L WITH CURL +0235: ȵ LATIN SMALL LETTER N WITH CURL +0236: ȶ LATIN SMALL LETTER T WITH CURL +0237: ȷ LATIN SMALL LETTER DOTLESS J +0238: ȸ LATIN SMALL LETTER DB DIGRAPH +0239: ȹ LATIN SMALL LETTER QP DIGRAPH +023A: Ⱥ LATIN CAPITAL LETTER A WITH STROKE +023B: Ȼ LATIN CAPITAL LETTER C WITH STROKE +023C: ȼ LATIN SMALL LETTER C WITH STROKE +023D: Ƚ LATIN CAPITAL LETTER L WITH BAR +023E: Ⱦ LATIN CAPITAL LETTER T WITH DIAGONAL STROKE +023F: ȿ LATIN SMALL LETTER S WITH SWASH TAIL +0240: ɀ LATIN SMALL LETTER Z WITH SWASH TAIL +0241: Ɂ LATIN CAPITAL LETTER GLOTTAL STOP +0242: ɂ LATIN SMALL LETTER GLOTTAL STOP +0243: Ƀ LATIN CAPITAL LETTER B WITH STROKE +0244: Ʉ LATIN CAPITAL LETTER U BAR +0245: Ʌ LATIN CAPITAL LETTER TURNED V +0246: Ɇ LATIN CAPITAL LETTER E WITH STROKE +0247: ɇ LATIN SMALL LETTER E WITH STROKE +0248: Ɉ LATIN CAPITAL LETTER J WITH STROKE +0249: ɉ LATIN SMALL LETTER J WITH STROKE +024A: Ɋ LATIN CAPITAL LETTER SMALL Q WITH HOOK TAIL +024B: ɋ LATIN SMALL LETTER Q WITH HOOK TAIL +024C: Ɍ LATIN CAPITAL LETTER R WITH STROKE +024D: ɍ LATIN SMALL LETTER R WITH STROKE +024E: Ɏ LATIN CAPITAL LETTER Y WITH STROKE +024F: ɏ LATIN SMALL LETTER Y WITH STROKE + +# Latin Extended Additional +1E00: Ḁ LATIN CAPITAL LETTER A WITH RING BELOW +1E01: ḁ LATIN SMALL LETTER A WITH RING BELOW +1E02: Ḃ LATIN CAPITAL LETTER B WITH DOT ABOVE +1E03: ḃ LATIN SMALL LETTER B WITH DOT ABOVE +1E04: Ḅ LATIN CAPITAL LETTER B WITH DOT BELOW +1E05: ḅ LATIN SMALL LETTER B WITH DOT BELOW +1E06: Ḇ LATIN CAPITAL LETTER B WITH LINE BELOW +1E07: ḇ LATIN SMALL LETTER B WITH LINE BELOW +1E08: Ḉ LATIN CAPITAL LETTER C WITH CEDILLA AND ACUTE +1E09: ḉ LATIN SMALL LETTER C WITH CEDILLA AND ACUTE +1E0A: Ḋ LATIN CAPITAL LETTER D WITH DOT ABOVE +1E0B: ḋ LATIN SMALL LETTER D WITH DOT ABOVE +1E0C: Ḍ LATIN CAPITAL LETTER D WITH DOT BELOW +1E0D: ḍ LATIN SMALL LETTER D WITH DOT BELOW +1E0E: Ḏ LATIN CAPITAL LETTER D WITH LINE BELOW +1E0F: ḏ LATIN SMALL LETTER D WITH LINE BELOW +1E10: Ḑ LATIN CAPITAL LETTER D WITH CEDILLA +1E11: ḑ LATIN SMALL LETTER D WITH CEDILLA +1E12: Ḓ LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW +1E13: ḓ LATIN SMALL LETTER D WITH CIRCUMFLEX BELOW +1E14: Ḕ LATIN CAPITAL LETTER E WITH MACRON AND GRAVE +1E15: ḕ LATIN SMALL LETTER E WITH MACRON AND GRAVE +1E16: Ḗ LATIN CAPITAL LETTER E WITH MACRON AND ACUTE +1E17: ḗ LATIN SMALL LETTER E WITH MACRON AND ACUTE +1E18: Ḙ LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW +1E19: ḙ LATIN SMALL LETTER E WITH CIRCUMFLEX BELOW +1E1A: Ḛ LATIN CAPITAL LETTER E WITH TILDE BELOW +1E1B: ḛ LATIN SMALL LETTER E WITH TILDE BELOW +1E1C: Ḝ LATIN CAPITAL LETTER E WITH CEDILLA AND BREVE +1E1D: ḝ LATIN SMALL LETTER E WITH CEDILLA AND BREVE +1E1E: Ḟ LATIN CAPITAL LETTER F WITH DOT ABOVE +1E1F: ḟ LATIN SMALL LETTER F WITH DOT ABOVE +1E20: Ḡ LATIN CAPITAL LETTER G WITH MACRON +1E21: ḡ LATIN SMALL LETTER G WITH MACRON +1E22: Ḣ LATIN CAPITAL LETTER H WITH DOT ABOVE +1E23: ḣ LATIN SMALL LETTER H WITH DOT ABOVE +1E24: Ḥ LATIN CAPITAL LETTER H WITH DOT BELOW +1E25: ḥ LATIN SMALL LETTER H WITH DOT BELOW +1E26: Ḧ LATIN CAPITAL LETTER H WITH DIAERESIS +1E27: ḧ LATIN SMALL LETTER H WITH DIAERESIS +1E28: Ḩ LATIN CAPITAL LETTER H WITH CEDILLA +1E29: ḩ LATIN SMALL LETTER H WITH CEDILLA +1E2A: Ḫ LATIN CAPITAL LETTER H WITH BREVE BELOW +1E2B: ḫ LATIN SMALL LETTER H WITH BREVE BELOW +1E2C: Ḭ LATIN CAPITAL LETTER I WITH TILDE BELOW +1E2D: ḭ LATIN SMALL LETTER I WITH TILDE BELOW +1E2E: Ḯ LATIN CAPITAL LETTER I WITH DIAERESIS AND ACUTE +1E2F: ḯ LATIN SMALL LETTER I WITH DIAERESIS AND ACUTE +1E30: Ḱ LATIN CAPITAL LETTER K WITH ACUTE +1E31: ḱ LATIN SMALL LETTER K WITH ACUTE +1E32: Ḳ LATIN CAPITAL LETTER K WITH DOT BELOW +1E33: ḳ LATIN SMALL LETTER K WITH DOT BELOW +1E34: Ḵ LATIN CAPITAL LETTER K WITH LINE BELOW +1E35: ḵ LATIN SMALL LETTER K WITH LINE BELOW +1E36: Ḷ LATIN CAPITAL LETTER L WITH DOT BELOW +1E37: ḷ LATIN SMALL LETTER L WITH DOT BELOW +1E38: Ḹ LATIN CAPITAL LETTER L WITH DOT BELOW AND MACRON +1E39: ḹ LATIN SMALL LETTER L WITH DOT BELOW AND MACRON +1E3A: Ḻ LATIN CAPITAL LETTER L WITH LINE BELOW +1E3B: ḻ LATIN SMALL LETTER L WITH LINE BELOW +1E3C: Ḽ LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW +1E3D: ḽ LATIN SMALL LETTER L WITH CIRCUMFLEX BELOW +1E3E: Ḿ LATIN CAPITAL LETTER M WITH ACUTE +1E3F: ḿ LATIN SMALL LETTER M WITH ACUTE +1E40: Ṁ LATIN CAPITAL LETTER M WITH DOT ABOVE +1E41: ṁ LATIN SMALL LETTER M WITH DOT ABOVE +1E42: Ṃ LATIN CAPITAL LETTER M WITH DOT BELOW +1E43: ṃ LATIN SMALL LETTER M WITH DOT BELOW +1E44: Ṅ LATIN CAPITAL LETTER N WITH DOT ABOVE +1E45: ṅ LATIN SMALL LETTER N WITH DOT ABOVE +1E46: Ṇ LATIN CAPITAL LETTER N WITH DOT BELOW +1E47: ṇ LATIN SMALL LETTER N WITH DOT BELOW +1E48: Ṉ LATIN CAPITAL LETTER N WITH LINE BELOW +1E49: ṉ LATIN SMALL LETTER N WITH LINE BELOW +1E4A: Ṋ LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW +1E4B: ṋ LATIN SMALL LETTER N WITH CIRCUMFLEX BELOW +1E4C: Ṍ LATIN CAPITAL LETTER O WITH TILDE AND ACUTE +1E4D: ṍ LATIN SMALL LETTER O WITH TILDE AND ACUTE +1E4E: Ṏ LATIN CAPITAL LETTER O WITH TILDE AND DIAERESIS +1E4F: ṏ LATIN SMALL LETTER O WITH TILDE AND DIAERESIS +1E50: Ṑ LATIN CAPITAL LETTER O WITH MACRON AND GRAVE +1E51: ṑ LATIN SMALL LETTER O WITH MACRON AND GRAVE +1E52: Ṓ LATIN CAPITAL LETTER O WITH MACRON AND ACUTE +1E53: ṓ LATIN SMALL LETTER O WITH MACRON AND ACUTE +1E54: Ṕ LATIN CAPITAL LETTER P WITH ACUTE +1E55: ṕ LATIN SMALL LETTER P WITH ACUTE +1E56: Ṗ LATIN CAPITAL LETTER P WITH DOT ABOVE +1E57: ṗ LATIN SMALL LETTER P WITH DOT ABOVE +1E58: Ṙ LATIN CAPITAL LETTER R WITH DOT ABOVE +1E59: ṙ LATIN SMALL LETTER R WITH DOT ABOVE +1E5A: Ṛ LATIN CAPITAL LETTER R WITH DOT BELOW +1E5B: ṛ LATIN SMALL LETTER R WITH DOT BELOW +1E5C: Ṝ LATIN CAPITAL LETTER R WITH DOT BELOW AND MACRON +1E5D: ṝ LATIN SMALL LETTER R WITH DOT BELOW AND MACRON +1E5E: Ṟ LATIN CAPITAL LETTER R WITH LINE BELOW +1E5F: ṟ LATIN SMALL LETTER R WITH LINE BELOW +1E60: Ṡ LATIN CAPITAL LETTER S WITH DOT ABOVE +1E61: ṡ LATIN SMALL LETTER S WITH DOT ABOVE +1E62: Ṣ LATIN CAPITAL LETTER S WITH DOT BELOW +1E63: ṣ LATIN SMALL LETTER S WITH DOT BELOW +1E64: Ṥ LATIN CAPITAL LETTER S WITH ACUTE AND DOT ABOVE +1E65: ṥ LATIN SMALL LETTER S WITH ACUTE AND DOT ABOVE +1E66: Ṧ LATIN CAPITAL LETTER S WITH CARON AND DOT ABOVE +1E67: ṧ LATIN SMALL LETTER S WITH CARON AND DOT ABOVE +1E68: Ṩ LATIN CAPITAL LETTER S WITH DOT BELOW AND DOT ABOVE +1E69: ṩ LATIN SMALL LETTER S WITH DOT BELOW AND DOT ABOVE +1E6A: Ṫ LATIN CAPITAL LETTER T WITH DOT ABOVE +1E6B: ṫ LATIN SMALL LETTER T WITH DOT ABOVE +1E6C: Ṭ LATIN CAPITAL LETTER T WITH DOT BELOW +1E6D: ṭ LATIN SMALL LETTER T WITH DOT BELOW +1E6E: Ṯ LATIN CAPITAL LETTER T WITH LINE BELOW +1E6F: ṯ LATIN SMALL LETTER T WITH LINE BELOW +1E70: Ṱ LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW +1E71: ṱ LATIN SMALL LETTER T WITH CIRCUMFLEX BELOW +1E72: Ṳ LATIN CAPITAL LETTER U WITH DIAERESIS BELOW +1E73: ṳ LATIN SMALL LETTER U WITH DIAERESIS BELOW +1E74: Ṵ LATIN CAPITAL LETTER U WITH TILDE BELOW +1E75: ṵ LATIN SMALL LETTER U WITH TILDE BELOW +1E76: Ṷ LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW +1E77: ṷ LATIN SMALL LETTER U WITH CIRCUMFLEX BELOW +1E78: Ṹ LATIN CAPITAL LETTER U WITH TILDE AND ACUTE +1E79: ṹ LATIN SMALL LETTER U WITH TILDE AND ACUTE +1E7A: Ṻ LATIN CAPITAL LETTER U WITH MACRON AND DIAERESIS +1E7B: ṻ LATIN SMALL LETTER U WITH MACRON AND DIAERESIS +1E7C: Ṽ LATIN CAPITAL LETTER V WITH TILDE +1E7D: ṽ LATIN SMALL LETTER V WITH TILDE +1E7E: Ṿ LATIN CAPITAL LETTER V WITH DOT BELOW +1E7F: ṿ LATIN SMALL LETTER V WITH DOT BELOW +1E80: Ẁ LATIN CAPITAL LETTER W WITH GRAVE +1E81: ẁ LATIN SMALL LETTER W WITH GRAVE +1E82: Ẃ LATIN CAPITAL LETTER W WITH ACUTE +1E83: ẃ LATIN SMALL LETTER W WITH ACUTE +1E84: Ẅ LATIN CAPITAL LETTER W WITH DIAERESIS +1E85: ẅ LATIN SMALL LETTER W WITH DIAERESIS +1E86: Ẇ LATIN CAPITAL LETTER W WITH DOT ABOVE +1E87: ẇ LATIN SMALL LETTER W WITH DOT ABOVE +1E88: Ẉ LATIN CAPITAL LETTER W WITH DOT BELOW +1E89: ẉ LATIN SMALL LETTER W WITH DOT BELOW +1E8A: Ẋ LATIN CAPITAL LETTER X WITH DOT ABOVE +1E8B: ẋ LATIN SMALL LETTER X WITH DOT ABOVE +1E8C: Ẍ LATIN CAPITAL LETTER X WITH DIAERESIS +1E8D: ẍ LATIN SMALL LETTER X WITH DIAERESIS +1E8E: Ẏ LATIN CAPITAL LETTER Y WITH DOT ABOVE +1E8F: ẏ LATIN SMALL LETTER Y WITH DOT ABOVE +1E90: Ẑ LATIN CAPITAL LETTER Z WITH CIRCUMFLEX +1E91: ẑ LATIN SMALL LETTER Z WITH CIRCUMFLEX +1E92: Ẓ LATIN CAPITAL LETTER Z WITH DOT BELOW +1E93: ẓ LATIN SMALL LETTER Z WITH DOT BELOW +1E94: Ẕ LATIN CAPITAL LETTER Z WITH LINE BELOW +1E95: ẕ LATIN SMALL LETTER Z WITH LINE BELOW +1E96: ẖ LATIN SMALL LETTER H WITH LINE BELOW +1E97: ẗ LATIN SMALL LETTER T WITH DIAERESIS +1E98: ẘ LATIN SMALL LETTER W WITH RING ABOVE +1E99: ẙ LATIN SMALL LETTER Y WITH RING ABOVE +1E9A: ẚ LATIN SMALL LETTER A WITH RIGHT HALF RING +1E9B: ẛ LATIN SMALL LETTER LONG S WITH DOT ABOVE +1E9C: ẜ LATIN SMALL LETTER LONG S WITH DIAGONAL STROKE +1E9D: ẝ LATIN SMALL LETTER LONG S WITH HIGH STROKE +1E9E: ẞ LATIN CAPITAL LETTER SHARP S +1E9F: ẟ LATIN SMALL LETTER DELTA +1EA0: Ạ LATIN CAPITAL LETTER A WITH DOT BELOW +1EA1: ạ LATIN SMALL LETTER A WITH DOT BELOW +1EA2: Ả LATIN CAPITAL LETTER A WITH HOOK ABOVE +1EA3: ả LATIN SMALL LETTER A WITH HOOK ABOVE +1EA4: Ấ LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE +1EA5: ấ LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE +1EA6: Ầ LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE +1EA7: ầ LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE +1EA8: Ẩ LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE +1EA9: ẩ LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE +1EAA: Ẫ LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE +1EAB: ẫ LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE +1EAC: Ậ LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW +1EAD: ậ LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW +1EAE: Ắ LATIN CAPITAL LETTER A WITH BREVE AND ACUTE +1EAF: ắ LATIN SMALL LETTER A WITH BREVE AND ACUTE +1EB0: Ằ LATIN CAPITAL LETTER A WITH BREVE AND GRAVE +1EB1: ằ LATIN SMALL LETTER A WITH BREVE AND GRAVE +1EB2: Ẳ LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE +1EB3: ẳ LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE +1EB4: Ẵ LATIN CAPITAL LETTER A WITH BREVE AND TILDE +1EB5: ẵ LATIN SMALL LETTER A WITH BREVE AND TILDE +1EB6: Ặ LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW +1EB7: ặ LATIN SMALL LETTER A WITH BREVE AND DOT BELOW +1EB8: Ẹ LATIN CAPITAL LETTER E WITH DOT BELOW +1EB9: ẹ LATIN SMALL LETTER E WITH DOT BELOW +1EBA: Ẻ LATIN CAPITAL LETTER E WITH HOOK ABOVE +1EBB: ẻ LATIN SMALL LETTER E WITH HOOK ABOVE +1EBC: Ẽ LATIN CAPITAL LETTER E WITH TILDE +1EBD: ẽ LATIN SMALL LETTER E WITH TILDE +1EBE: Ế LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE +1EBF: ế LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE +1EC0: Ề LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE +1EC1: ề LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE +1EC2: Ể LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE +1EC3: ể LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE +1EC4: Ễ LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE +1EC5: ễ LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE +1EC6: Ệ LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW +1EC7: ệ LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW +1EC8: Ỉ LATIN CAPITAL LETTER I WITH HOOK ABOVE +1EC9: ỉ LATIN SMALL LETTER I WITH HOOK ABOVE +1ECA: Ị LATIN CAPITAL LETTER I WITH DOT BELOW +1ECB: ị LATIN SMALL LETTER I WITH DOT BELOW +1ECC: Ọ LATIN CAPITAL LETTER O WITH DOT BELOW +1ECD: ọ LATIN SMALL LETTER O WITH DOT BELOW +1ECE: Ỏ LATIN CAPITAL LETTER O WITH HOOK ABOVE +1ECF: ỏ LATIN SMALL LETTER O WITH HOOK ABOVE +1ED0: Ố LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE +1ED1: ố LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACUTE +1ED2: Ồ LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE +1ED3: ồ LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRAVE +1ED4: Ổ LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE +1ED5: ổ LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE +1ED6: Ỗ LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE +1ED7: ỗ LATIN SMALL LETTER O WITH CIRCUMFLEX AND TILDE +1ED8: Ộ LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW +1ED9: ộ LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW +1EDA: Ớ LATIN CAPITAL LETTER O WITH HORN AND ACUTE +1EDB: ớ LATIN SMALL LETTER O WITH HORN AND ACUTE +1EDC: Ờ LATIN CAPITAL LETTER O WITH HORN AND GRAVE +1EDD: ờ LATIN SMALL LETTER O WITH HORN AND GRAVE +1EDE: Ở LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE +1EDF: ở LATIN SMALL LETTER O WITH HORN AND HOOK ABOVE +1EE0: Ỡ LATIN CAPITAL LETTER O WITH HORN AND TILDE +1EE1: ỡ LATIN SMALL LETTER O WITH HORN AND TILDE +1EE2: Ợ LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW +1EE3: ợ LATIN SMALL LETTER O WITH HORN AND DOT BELOW +1EE4: Ụ LATIN CAPITAL LETTER U WITH DOT BELOW +1EE5: ụ LATIN SMALL LETTER U WITH DOT BELOW +1EE6: Ủ LATIN CAPITAL LETTER U WITH HOOK ABOVE +1EE7: ủ LATIN SMALL LETTER U WITH HOOK ABOVE +1EE8: Ứ LATIN CAPITAL LETTER U WITH HORN AND ACUTE +1EE9: ứ LATIN SMALL LETTER U WITH HORN AND ACUTE +1EEA: Ừ LATIN CAPITAL LETTER U WITH HORN AND GRAVE +1EEB: ừ LATIN SMALL LETTER U WITH HORN AND GRAVE +1EEC: Ử LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE +1EED: ử LATIN SMALL LETTER U WITH HORN AND HOOK ABOVE +1EEE: Ữ LATIN CAPITAL LETTER U WITH HORN AND TILDE +1EEF: ữ LATIN SMALL LETTER U WITH HORN AND TILDE +1EF0: Ự LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW +1EF1: ự LATIN SMALL LETTER U WITH HORN AND DOT BELOW +1EF2: Ỳ LATIN CAPITAL LETTER Y WITH GRAVE +1EF3: ỳ LATIN SMALL LETTER Y WITH GRAVE +1EF4: Ỵ LATIN CAPITAL LETTER Y WITH DOT BELOW +1EF5: ỵ LATIN SMALL LETTER Y WITH DOT BELOW +1EF6: Ỷ LATIN CAPITAL LETTER Y WITH HOOK ABOVE +1EF7: ỷ LATIN SMALL LETTER Y WITH HOOK ABOVE +1EF8: Ỹ LATIN CAPITAL LETTER Y WITH TILDE +1EF9: ỹ LATIN SMALL LETTER Y WITH TILDE +1EFA: Ỻ LATIN CAPITAL LETTER MIDDLE-WELSH LL +1EFB: ỻ LATIN SMALL LETTER MIDDLE-WELSH LL +1EFC: Ỽ LATIN CAPITAL LETTER MIDDLE-WELSH V +1EFD: ỽ LATIN SMALL LETTER MIDDLE-WELSH V +1EFE: Ỿ LATIN CAPITAL LETTER Y WITH LOOP +1EFF: ỿ LATIN SMALL LETTER Y WITH LOOP + +# Spacing Modifier Letters +02B0: ʰ MODIFIER LETTER SMALL H +02B1: ʱ MODIFIER LETTER SMALL H WITH HOOK +02B2: ʲ MODIFIER LETTER SMALL J +02B3: ʳ MODIFIER LETTER SMALL R +02B4: ʴ MODIFIER LETTER SMALL TURNED R +02B5: ʵ MODIFIER LETTER SMALL TURNED R WITH HOOK +02B6: ʶ MODIFIER LETTER SMALL CAPITAL INVERTED R +02B7: ʷ MODIFIER LETTER SMALL W +02B8: ʸ MODIFIER LETTER SMALL Y +02B9: ʹ MODIFIER LETTER PRIME +02BA: ʺ MODIFIER LETTER DOUBLE PRIME +02BB: ʻ MODIFIER LETTER TURNED COMMA +02BC: ʼ MODIFIER LETTER APOSTROPHE +02BD: ʽ MODIFIER LETTER REVERSED COMMA +02BE: ʾ MODIFIER LETTER RIGHT HALF RING +02BF: ʿ MODIFIER LETTER LEFT HALF RING +02C0: ˀ MODIFIER LETTER GLOTTAL STOP +02C1: ˁ MODIFIER LETTER REVERSED GLOTTAL STOP +02C2: ˂ MODIFIER LETTER LEFT ARROWHEAD +02C3: ˃ MODIFIER LETTER RIGHT ARROWHEAD +02C4: ˄ MODIFIER LETTER UP ARROWHEAD +02C5: ˅ MODIFIER LETTER DOWN ARROWHEAD +02C6: ˆ MODIFIER LETTER CIRCUMFLEX ACCENT +02C7: ˇ CARON +02C8: ˈ MODIFIER LETTER VERTICAL LINE +02C9: ˉ MODIFIER LETTER MACRON +02CA: ˊ MODIFIER LETTER ACUTE ACCENT +02CB: ˋ MODIFIER LETTER GRAVE ACCENT +02CC: ˌ MODIFIER LETTER LOW VERTICAL LINE +02CD: ˍ MODIFIER LETTER LOW MACRON +02CE: ˎ MODIFIER LETTER LOW GRAVE ACCENT +02CF: ˏ MODIFIER LETTER LOW ACUTE ACCENT +02D0: ː MODIFIER LETTER TRIANGULAR COLON +02D1: ˑ MODIFIER LETTER HALF TRIANGULAR COLON +02D2: ˒ MODIFIER LETTER CENTRED RIGHT HALF RING +02D3: ˓ MODIFIER LETTER CENTRED LEFT HALF RING +02D4: ˔ MODIFIER LETTER UP TACK +02D5: ˕ MODIFIER LETTER DOWN TACK +02D6: ˖ MODIFIER LETTER PLUS SIGN +02D7: ˗ MODIFIER LETTER MINUS SIGN +02D8: ˘ BREVE +02D9: ˙ DOT ABOVE +02DA: ˚ RING ABOVE +02DB: ˛ OGONEK +02DC: ˜ SMALL TILDE +02DD: ˝ DOUBLE ACUTE ACCENT +02DE: ˞ MODIFIER LETTER RHOTIC HOOK +02DF: ˟ MODIFIER LETTER CROSS ACCENT +02E0: ˠ MODIFIER LETTER SMALL GAMMA +02E1: ˡ MODIFIER LETTER SMALL L +02E2: ˢ MODIFIER LETTER SMALL S +02E3: ˣ MODIFIER LETTER SMALL X +02E4: ˤ MODIFIER LETTER SMALL REVERSED GLOTTAL STOP +02E5: ˥ MODIFIER LETTER EXTRA-HIGH TONE BAR +02E6: ˦ MODIFIER LETTER HIGH TONE BAR +02E7: ˧ MODIFIER LETTER MID TONE BAR +02E8: ˨ MODIFIER LETTER LOW TONE BAR +02E9: ˩ MODIFIER LETTER EXTRA-LOW TONE BAR +02EA: ˪ MODIFIER LETTER YIN DEPARTING TONE MARK +02EB: ˫ MODIFIER LETTER YANG DEPARTING TONE MARK +02EC: ˬ MODIFIER LETTER VOICING +02ED: ˭ MODIFIER LETTER UNASPIRATED +02EE: ˮ MODIFIER LETTER DOUBLE APOSTROPHE +02EF: ˯ MODIFIER LETTER LOW DOWN ARROWHEAD +02F0: ˰ MODIFIER LETTER LOW UP ARROWHEAD +02F1: ˱ MODIFIER LETTER LOW LEFT ARROWHEAD +02F2: ˲ MODIFIER LETTER LOW RIGHT ARROWHEAD +02F3: ˳ MODIFIER LETTER LOW RING +02F4: ˴ MODIFIER LETTER MIDDLE GRAVE ACCENT +02F5: ˵ MODIFIER LETTER MIDDLE DOUBLE GRAVE ACCENT +02F6: ˶ MODIFIER LETTER MIDDLE DOUBLE ACUTE ACCENT +02F7: ˷ MODIFIER LETTER LOW TILDE +02F8: ˸ MODIFIER LETTER RAISED COLON +02F9: ˹ MODIFIER LETTER BEGIN HIGH TONE +02FA: ˺ MODIFIER LETTER END HIGH TONE +02FB: ˻ MODIFIER LETTER BEGIN LOW TONE +02FC: ˼ MODIFIER LETTER END LOW TONE +02FD: ˽ MODIFIER LETTER SHELF +02FE: ˾ MODIFIER LETTER OPEN SHELF +02FF: ˿ MODIFIER LETTER LOW LEFT ARROW + +# General Punctuation +2000:   EN QUAD +2001:   EM QUAD +2002:   EN SPACE +2003:   EM SPACE +2004:   THREE-PER-EM SPACE +2005:   FOUR-PER-EM SPACE +2006:   SIX-PER-EM SPACE +2007:   FIGURE SPACE +2008:   PUNCTUATION SPACE +2009:   THIN SPACE +200A:   HAIR SPACE +200B: ​ ZERO WIDTH SPACE +200C: ‌ ZERO WIDTH NON-JOINER +200D: ‍ ZERO WIDTH JOINER +200E: ‎ LEFT-TO-RIGHT MARK +200F: ‏ RIGHT-TO-LEFT MARK +2010: ‐ HYPHEN +2011: ‑ NON-BREAKING HYPHEN +2012: ‒ FIGURE DASH +2013: – EN DASH +2014: — EM DASH +2015: ― HORIZONTAL BAR +2016: ‖ DOUBLE VERTICAL LINE +2017: ‗ DOUBLE LOW LINE +2018: ‘ LEFT SINGLE QUOTATION MARK +2019: ’ RIGHT SINGLE QUOTATION MARK +201A: ‚ SINGLE LOW-9 QUOTATION MARK +201B: ‛ SINGLE HIGH-REVERSED-9 QUOTATION MARK +201C: “ LEFT DOUBLE QUOTATION MARK +201D: ” RIGHT DOUBLE QUOTATION MARK +201E: „ DOUBLE LOW-9 QUOTATION MARK +201F: ‟ DOUBLE HIGH-REVERSED-9 QUOTATION MARK +2020: † DAGGER +2021: ‡ DOUBLE DAGGER +2022: • BULLET +2023: ‣ TRIANGULAR BULLET +2024: ․ ONE DOT LEADER +2025: ‥ TWO DOT LEADER +2026: … HORIZONTAL ELLIPSIS +2027: ‧ HYPHENATION POINT +2028: 
 LINE SEPARATOR +2029: 
 PARAGRAPH SEPARATOR +202A: ‪ LEFT-TO-RIGHT EMBEDDING +202B: ‫ RIGHT-TO-LEFT EMBEDDING +202C: ‬ POP DIRECTIONAL FORMATTING +202D: ‭ LEFT-TO-RIGHT OVERRIDE +202E: ‮ RIGHT-TO-LEFT OVERRIDE +202F:   NARROW NO-BREAK SPACE +2030: ‰ PER MILLE SIGN +2031: ‱ PER TEN THOUSAND SIGN +2032: ′ PRIME +2033: ″ DOUBLE PRIME +2034: ‴ TRIPLE PRIME +2035: ‵ REVERSED PRIME +2036: ‶ REVERSED DOUBLE PRIME +2037: ‷ REVERSED TRIPLE PRIME +2038: ‸ CARET +2039: ‹ SINGLE LEFT-POINTING ANGLE QUOTATION MARK +203A: › SINGLE RIGHT-POINTING ANGLE QUOTATION MARK +203B: ※ REFERENCE MARK +203C: ‼ DOUBLE EXCLAMATION MARK +203D: ‽ INTERROBANG +203E: ‾ OVERLINE +203F: ‿ UNDERTIE +2040: ⁀ CHARACTER TIE +2041: ⁁ CARET INSERTION POINT +2042: ⁂ ASTERISM +2043: ⁃ HYPHEN BULLET +2044: ⁄ FRACTION SLASH +2045: ⁅ LEFT SQUARE BRACKET WITH QUILL +2046: ⁆ RIGHT SQUARE BRACKET WITH QUILL +2047: ⁇ DOUBLE QUESTION MARK +2048: ⁈ QUESTION EXCLAMATION MARK +2049: ⁉ EXCLAMATION QUESTION MARK +204A: ⁊ TIRONIAN SIGN ET +204B: ⁋ REVERSED PILCROW SIGN +204C: ⁌ BLACK LEFTWARDS BULLET +204D: ⁍ BLACK RIGHTWARDS BULLET +204E: ⁎ LOW ASTERISK +204F: ⁏ REVERSED SEMICOLON +2050: ⁐ CLOSE UP +2051: ⁑ TWO ASTERISKS ALIGNED VERTICALLY +2052: ⁒ COMMERCIAL MINUS SIGN +2053: ⁓ SWUNG DASH +2054: ⁔ INVERTED UNDERTIE +2055: ⁕ FLOWER PUNCTUATION MARK +2056: ⁖ THREE DOT PUNCTUATION +2057: ⁗ QUADRUPLE PRIME +2058: ⁘ FOUR DOT PUNCTUATION +2059: ⁙ FIVE DOT PUNCTUATION +205A: ⁚ TWO DOT PUNCTUATION +205B: ⁛ FOUR DOT MARK +205C: ⁜ DOTTED CROSS +205D: ⁝ TRICOLON +205E: ⁞ VERTICAL FOUR DOTS +205F:   MEDIUM MATHEMATICAL SPACE +2060: ⁠ WORD JOINER +2061: ⁡ FUNCTION APPLICATION +2062: ⁢ INVISIBLE TIMES +2063: ⁣ INVISIBLE SEPARATOR +2064: ⁤ INVISIBLE PLUS +206A:  INHIBIT SYMMETRIC SWAPPING +206B:  ACTIVATE SYMMETRIC SWAPPING +206C:  INHIBIT ARABIC FORM SHAPING +206D:  ACTIVATE ARABIC FORM SHAPING +206E:  NATIONAL DIGIT SHAPES +206F:  NOMINAL DIGIT SHAPES + +# Combining Diacritical Marks +0300: ̀ COMBINING GRAVE ACCENT +0301: ́ COMBINING ACUTE ACCENT +0302: ̂ COMBINING CIRCUMFLEX ACCENT +0303: ̃ COMBINING TILDE +0304: ̄ COMBINING MACRON +0305: ̅ COMBINING OVERLINE +0306: ̆ COMBINING BREVE +0307: ̇ COMBINING DOT ABOVE +0308: ̈ COMBINING DIAERESIS +0309: ̉ COMBINING HOOK ABOVE +030A: ̊ COMBINING RING ABOVE +030B: ̋ COMBINING DOUBLE ACUTE ACCENT +030C: ̌ COMBINING CARON +030D: ̍ COMBINING VERTICAL LINE ABOVE +030E: ̎ COMBINING DOUBLE VERTICAL LINE ABOVE +030F: ̏ COMBINING DOUBLE GRAVE ACCENT +0310: ̐ COMBINING CANDRABINDU +0311: ̑ COMBINING INVERTED BREVE +0312: ̒ COMBINING TURNED COMMA ABOVE +0313: ̓ COMBINING COMMA ABOVE +0314: ̔ COMBINING REVERSED COMMA ABOVE +0315: ̕ COMBINING COMMA ABOVE RIGHT +0316: ̖ COMBINING GRAVE ACCENT BELOW +0317: ̗ COMBINING ACUTE ACCENT BELOW +0318: ̘ COMBINING LEFT TACK BELOW +0319: ̙ COMBINING RIGHT TACK BELOW +031A: ̚ COMBINING LEFT ANGLE ABOVE +031B: ̛ COMBINING HORN +031C: ̜ COMBINING LEFT HALF RING BELOW +031D: ̝ COMBINING UP TACK BELOW +031E: ̞ COMBINING DOWN TACK BELOW +031F: ̟ COMBINING PLUS SIGN BELOW +0320: ̠ COMBINING MINUS SIGN BELOW +0321: ̡ COMBINING PALATALIZED HOOK BELOW +0322: ̢ COMBINING RETROFLEX HOOK BELOW +0323: ̣ COMBINING DOT BELOW +0324: ̤ COMBINING DIAERESIS BELOW +0325: ̥ COMBINING RING BELOW +0326: ̦ COMBINING COMMA BELOW +0327: ̧ COMBINING CEDILLA +0328: ̨ COMBINING OGONEK +0329: ̩ COMBINING VERTICAL LINE BELOW +032A: ̪ COMBINING BRIDGE BELOW +032B: ̫ COMBINING INVERTED DOUBLE ARCH BELOW +032C: ̬ COMBINING CARON BELOW +032D: ̭ COMBINING CIRCUMFLEX ACCENT BELOW +032E: ̮ COMBINING BREVE BELOW +032F: ̯ COMBINING INVERTED BREVE BELOW +0330: ̰ COMBINING TILDE BELOW +0331: ̱ COMBINING MACRON BELOW +0332: ̲ COMBINING LOW LINE +0333: ̳ COMBINING DOUBLE LOW LINE +0334: ̴ COMBINING TILDE OVERLAY +0335: ̵ COMBINING SHORT STROKE OVERLAY +0336: ̶ COMBINING LONG STROKE OVERLAY +0337: ̷ COMBINING SHORT SOLIDUS OVERLAY +0338: ̸ COMBINING LONG SOLIDUS OVERLAY +0339: ̹ COMBINING RIGHT HALF RING BELOW +033A: ̺ COMBINING INVERTED BRIDGE BELOW +033B: ̻ COMBINING SQUARE BELOW +033C: ̼ COMBINING SEAGULL BELOW +033D: ̽ COMBINING X ABOVE +033E: ̾ COMBINING VERTICAL TILDE +033F: ̿ COMBINING DOUBLE OVERLINE +0340: ̀ COMBINING GRAVE TONE MARK +0341: ́ COMBINING ACUTE TONE MARK +0342: ͂ COMBINING GREEK PERISPOMENI +0343: ̓ COMBINING GREEK KORONIS +0344: ̈́ COMBINING GREEK DIALYTIKA TONOS +0345: ͅ COMBINING GREEK YPOGEGRAMMENI +0346: ͆ COMBINING BRIDGE ABOVE +0347: ͇ COMBINING EQUALS SIGN BELOW +0348: ͈ COMBINING DOUBLE VERTICAL LINE BELOW +0349: ͉ COMBINING LEFT ANGLE BELOW +034A: ͊ COMBINING NOT TILDE ABOVE +034B: ͋ COMBINING HOMOTHETIC ABOVE +034C: ͌ COMBINING ALMOST EQUAL TO ABOVE +034D: ͍ COMBINING LEFT RIGHT ARROW BELOW +034E: ͎ COMBINING UPWARDS ARROW BELOW +034F: ͏ COMBINING GRAPHEME JOINER +0350: ͐ COMBINING RIGHT ARROWHEAD ABOVE +0351: ͑ COMBINING LEFT HALF RING ABOVE +0352: ͒ COMBINING FERMATA +0353: ͓ COMBINING X BELOW +0354: ͔ COMBINING LEFT ARROWHEAD BELOW +0355: ͕ COMBINING RIGHT ARROWHEAD BELOW +0356: ͖ COMBINING RIGHT ARROWHEAD AND UP ARROWHEAD BELOW +0357: ͗ COMBINING RIGHT HALF RING ABOVE +0358: ͘ COMBINING DOT ABOVE RIGHT +0359: ͙ COMBINING ASTERISK BELOW +035A: ͚ COMBINING DOUBLE RING BELOW +035B: ͛ COMBINING ZIGZAG ABOVE +035C: ͜ COMBINING DOUBLE BREVE BELOW +035D: ͝ COMBINING DOUBLE BREVE +035E: ͞ COMBINING DOUBLE MACRON +035F: ͟ COMBINING DOUBLE MACRON BELOW +0360: ͠ COMBINING DOUBLE TILDE +0361: ͡ COMBINING DOUBLE INVERTED BREVE +0362: ͢ COMBINING DOUBLE RIGHTWARDS ARROW BELOW +0363: ͣ COMBINING LATIN SMALL LETTER A +0364: ͤ COMBINING LATIN SMALL LETTER E +0365: ͥ COMBINING LATIN SMALL LETTER I +0366: ͦ COMBINING LATIN SMALL LETTER O +0367: ͧ COMBINING LATIN SMALL LETTER U +0368: ͨ COMBINING LATIN SMALL LETTER C +0369: ͩ COMBINING LATIN SMALL LETTER D +036A: ͪ COMBINING LATIN SMALL LETTER H +036B: ͫ COMBINING LATIN SMALL LETTER M +036C: ͬ COMBINING LATIN SMALL LETTER R +036D: ͭ COMBINING LATIN SMALL LETTER T +036E: ͮ COMBINING LATIN SMALL LETTER V +036F: ͯ COMBINING LATIN SMALL LETTER X + +# Mathematical Operators +2200: ∀ FOR ALL +2201: ∁ COMPLEMENT +2202: ∂ PARTIAL DIFFERENTIAL +2203: ∃ THERE EXISTS +2204: ∄ THERE DOES NOT EXIST +2205: ∅ EMPTY SET +2206: ∆ INCREMENT +2207: ∇ NABLA +2208: ∈ ELEMENT OF +2209: ∉ NOT AN ELEMENT OF +220A: ∊ SMALL ELEMENT OF +220B: ∋ CONTAINS AS MEMBER +220C: ∌ DOES NOT CONTAIN AS MEMBER +220D: ∍ SMALL CONTAINS AS MEMBER +220E: ∎ END OF PROOF +220F: ∏ N-ARY PRODUCT +2210: ∐ N-ARY COPRODUCT +2211: ∑ N-ARY SUMMATION +2212: − MINUS SIGN +2213: ∓ MINUS-OR-PLUS SIGN +2214: ∔ DOT PLUS +2215: ∕ DIVISION SLASH +2216: ∖ SET MINUS +2217: ∗ ASTERISK OPERATOR +2218: ∘ RING OPERATOR +2219: ∙ BULLET OPERATOR +221A: √ SQUARE ROOT +221B: ∛ CUBE ROOT +221C: ∜ FOURTH ROOT +221D: ∝ PROPORTIONAL TO +221E: ∞ INFINITY +221F: ∟ RIGHT ANGLE +2220: ∠ ANGLE +2221: ∡ MEASURED ANGLE +2222: ∢ SPHERICAL ANGLE +2223: ∣ DIVIDES +2224: ∤ DOES NOT DIVIDE +2225: ∥ PARALLEL TO +2226: ∦ NOT PARALLEL TO +2227: ∧ LOGICAL AND +2228: ∨ LOGICAL OR +2229: ∩ INTERSECTION +222A: ∪ UNION +222B: ∫ INTEGRAL +222C: ∬ DOUBLE INTEGRAL +222D: ∭ TRIPLE INTEGRAL +222E: ∮ CONTOUR INTEGRAL +222F: ∯ SURFACE INTEGRAL +2230: ∰ VOLUME INTEGRAL +2231: ∱ CLOCKWISE INTEGRAL +2232: ∲ CLOCKWISE CONTOUR INTEGRAL +2233: ∳ ANTICLOCKWISE CONTOUR INTEGRAL +2234: ∴ THEREFORE +2235: ∵ BECAUSE +2236: ∶ RATIO +2237: ∷ PROPORTION +2238: ∸ DOT MINUS +2239: ∹ EXCESS +223A: ∺ GEOMETRIC PROPORTION +223B: ∻ HOMOTHETIC +223C: ∼ TILDE OPERATOR +223D: ∽ REVERSED TILDE +223E: ∾ INVERTED LAZY S +223F: ∿ SINE WAVE +2240: ≀ WREATH PRODUCT +2241: ≁ NOT TILDE +2242: ≂ MINUS TILDE +2243: ≃ ASYMPTOTICALLY EQUAL TO +2244: ≄ NOT ASYMPTOTICALLY EQUAL TO +2245: ≅ APPROXIMATELY EQUAL TO +2246: ≆ APPROXIMATELY BUT NOT ACTUALLY EQUAL TO +2247: ≇ NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO +2248: ≈ ALMOST EQUAL TO +2249: ≉ NOT ALMOST EQUAL TO +224A: ≊ ALMOST EQUAL OR EQUAL TO +224B: ≋ TRIPLE TILDE +224C: ≌ ALL EQUAL TO +224D: ≍ EQUIVALENT TO +224E: ≎ GEOMETRICALLY EQUIVALENT TO +224F: ≏ DIFFERENCE BETWEEN +2250: ≐ APPROACHES THE LIMIT +2251: ≑ GEOMETRICALLY EQUAL TO +2252: ≒ APPROXIMATELY EQUAL TO OR THE IMAGE OF +2253: ≓ IMAGE OF OR APPROXIMATELY EQUAL TO +2254: ≔ COLON EQUALS +2255: ≕ EQUALS COLON +2256: ≖ RING IN EQUAL TO +2257: ≗ RING EQUAL TO +2258: ≘ CORRESPONDS TO +2259: ≙ ESTIMATES +225A: ≚ EQUIANGULAR TO +225B: ≛ STAR EQUALS +225C: ≜ DELTA EQUAL TO +225D: ≝ EQUAL TO BY DEFINITION +225E: ≞ MEASURED BY +225F: ≟ QUESTIONED EQUAL TO +2260: ≠ NOT EQUAL TO +2261: ≡ IDENTICAL TO +2262: ≢ NOT IDENTICAL TO +2263: ≣ STRICTLY EQUIVALENT TO +2264: ≤ LESS-THAN OR EQUAL TO +2265: ≥ GREATER-THAN OR EQUAL TO +2266: ≦ LESS-THAN OVER EQUAL TO +2267: ≧ GREATER-THAN OVER EQUAL TO +2268: ≨ LESS-THAN BUT NOT EQUAL TO +2269: ≩ GREATER-THAN BUT NOT EQUAL TO +226A: ≪ MUCH LESS-THAN +226B: ≫ MUCH GREATER-THAN +226C: ≬ BETWEEN +226D: ≭ NOT EQUIVALENT TO +226E: ≮ NOT LESS-THAN +226F: ≯ NOT GREATER-THAN +2270: ≰ NEITHER LESS-THAN NOR EQUAL TO +2271: ≱ NEITHER GREATER-THAN NOR EQUAL TO +2272: ≲ LESS-THAN OR EQUIVALENT TO +2273: ≳ GREATER-THAN OR EQUIVALENT TO +2274: ≴ NEITHER LESS-THAN NOR EQUIVALENT TO +2275: ≵ NEITHER GREATER-THAN NOR EQUIVALENT TO +2276: ≶ LESS-THAN OR GREATER-THAN +2277: ≷ GREATER-THAN OR LESS-THAN +2278: ≸ NEITHER LESS-THAN NOR GREATER-THAN +2279: ≹ NEITHER GREATER-THAN NOR LESS-THAN +227A: ≺ PRECEDES +227B: ≻ SUCCEEDS +227C: ≼ PRECEDES OR EQUAL TO +227D: ≽ SUCCEEDS OR EQUAL TO +227E: ≾ PRECEDES OR EQUIVALENT TO +227F: ≿ SUCCEEDS OR EQUIVALENT TO +2280: ⊀ DOES NOT PRECEDE +2281: ⊁ DOES NOT SUCCEED +2282: ⊂ SUBSET OF +2283: ⊃ SUPERSET OF +2284: ⊄ NOT A SUBSET OF +2285: ⊅ NOT A SUPERSET OF +2286: ⊆ SUBSET OF OR EQUAL TO +2287: ⊇ SUPERSET OF OR EQUAL TO +2288: ⊈ NEITHER A SUBSET OF NOR EQUAL TO +2289: ⊉ NEITHER A SUPERSET OF NOR EQUAL TO +228A: ⊊ SUBSET OF WITH NOT EQUAL TO +228B: ⊋ SUPERSET OF WITH NOT EQUAL TO +228C: ⊌ MULTISET +228D: ⊍ MULTISET MULTIPLICATION +228E: ⊎ MULTISET UNION +228F: ⊏ SQUARE IMAGE OF +2290: ⊐ SQUARE ORIGINAL OF +2291: ⊑ SQUARE IMAGE OF OR EQUAL TO +2292: ⊒ SQUARE ORIGINAL OF OR EQUAL TO +2293: ⊓ SQUARE CAP +2294: ⊔ SQUARE CUP +2295: ⊕ CIRCLED PLUS +2296: ⊖ CIRCLED MINUS +2297: ⊗ CIRCLED TIMES +2298: ⊘ CIRCLED DIVISION SLASH +2299: ⊙ CIRCLED DOT OPERATOR +229A: ⊚ CIRCLED RING OPERATOR +229B: ⊛ CIRCLED ASTERISK OPERATOR +229C: ⊜ CIRCLED EQUALS +229D: ⊝ CIRCLED DASH +229E: ⊞ SQUARED PLUS +229F: ⊟ SQUARED MINUS +22A0: ⊠ SQUARED TIMES +22A1: ⊡ SQUARED DOT OPERATOR +22A2: ⊢ RIGHT TACK +22A3: ⊣ LEFT TACK +22A4: ⊤ DOWN TACK +22A5: ⊥ UP TACK +22A6: ⊦ ASSERTION +22A7: ⊧ MODELS +22A8: ⊨ TRUE +22A9: ⊩ FORCES +22AA: ⊪ TRIPLE VERTICAL BAR RIGHT TURNSTILE +22AB: ⊫ DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE +22AC: ⊬ DOES NOT PROVE +22AD: ⊭ NOT TRUE +22AE: ⊮ DOES NOT FORCE +22AF: ⊯ NEGATED DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE +22B0: ⊰ PRECEDES UNDER RELATION +22B1: ⊱ SUCCEEDS UNDER RELATION +22B2: ⊲ NORMAL SUBGROUP OF +22B3: ⊳ CONTAINS AS NORMAL SUBGROUP +22B4: ⊴ NORMAL SUBGROUP OF OR EQUAL TO +22B5: ⊵ CONTAINS AS NORMAL SUBGROUP OR EQUAL TO +22B6: ⊶ ORIGINAL OF +22B7: ⊷ IMAGE OF +22B8: ⊸ MULTIMAP +22B9: ⊹ HERMITIAN CONJUGATE MATRIX +22BA: ⊺ INTERCALATE +22BB: ⊻ XOR +22BC: ⊼ NAND +22BD: ⊽ NOR +22BE: ⊾ RIGHT ANGLE WITH ARC +22BF: ⊿ RIGHT TRIANGLE +22C0: ⋀ N-ARY LOGICAL AND +22C1: ⋁ N-ARY LOGICAL OR +22C2: ⋂ N-ARY INTERSECTION +22C3: ⋃ N-ARY UNION +22C4: ⋄ DIAMOND OPERATOR +22C5: ⋅ DOT OPERATOR +22C6: ⋆ STAR OPERATOR +22C7: ⋇ DIVISION TIMES +22C8: ⋈ BOWTIE +22C9: ⋉ LEFT NORMAL FACTOR SEMIDIRECT PRODUCT +22CA: ⋊ RIGHT NORMAL FACTOR SEMIDIRECT PRODUCT +22CB: ⋋ LEFT SEMIDIRECT PRODUCT +22CC: ⋌ RIGHT SEMIDIRECT PRODUCT +22CD: ⋍ REVERSED TILDE EQUALS +22CE: ⋎ CURLY LOGICAL OR +22CF: ⋏ CURLY LOGICAL AND +22D0: ⋐ DOUBLE SUBSET +22D1: ⋑ DOUBLE SUPERSET +22D2: ⋒ DOUBLE INTERSECTION +22D3: ⋓ DOUBLE UNION +22D4: ⋔ PITCHFORK +22D5: ⋕ EQUAL AND PARALLEL TO +22D6: ⋖ LESS-THAN WITH DOT +22D7: ⋗ GREATER-THAN WITH DOT +22D8: ⋘ VERY MUCH LESS-THAN +22D9: ⋙ VERY MUCH GREATER-THAN +22DA: ⋚ LESS-THAN EQUAL TO OR GREATER-THAN +22DB: ⋛ GREATER-THAN EQUAL TO OR LESS-THAN +22DC: ⋜ EQUAL TO OR LESS-THAN +22DD: ⋝ EQUAL TO OR GREATER-THAN +22DE: ⋞ EQUAL TO OR PRECEDES +22DF: ⋟ EQUAL TO OR SUCCEEDS +22E0: ⋠ DOES NOT PRECEDE OR EQUAL +22E1: ⋡ DOES NOT SUCCEED OR EQUAL +22E2: ⋢ NOT SQUARE IMAGE OF OR EQUAL TO +22E3: ⋣ NOT SQUARE ORIGINAL OF OR EQUAL TO +22E4: ⋤ SQUARE IMAGE OF OR NOT EQUAL TO +22E5: ⋥ SQUARE ORIGINAL OF OR NOT EQUAL TO +22E6: ⋦ LESS-THAN BUT NOT EQUIVALENT TO +22E7: ⋧ GREATER-THAN BUT NOT EQUIVALENT TO +22E8: ⋨ PRECEDES BUT NOT EQUIVALENT TO +22E9: ⋩ SUCCEEDS BUT NOT EQUIVALENT TO +22EA: ⋪ NOT NORMAL SUBGROUP OF +22EB: ⋫ DOES NOT CONTAIN AS NORMAL SUBGROUP +22EC: ⋬ NOT NORMAL SUBGROUP OF OR EQUAL TO +22ED: ⋭ DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL +22EE: ⋮ VERTICAL ELLIPSIS +22EF: ⋯ MIDLINE HORIZONTAL ELLIPSIS +22F0: ⋰ UP RIGHT DIAGONAL ELLIPSIS +22F1: ⋱ DOWN RIGHT DIAGONAL ELLIPSIS +22F2: ⋲ ELEMENT OF WITH LONG HORIZONTAL STROKE +22F3: ⋳ ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE +22F4: ⋴ SMALL ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE +22F5: ⋵ ELEMENT OF WITH DOT ABOVE +22F6: ⋶ ELEMENT OF WITH OVERBAR +22F7: ⋷ SMALL ELEMENT OF WITH OVERBAR +22F8: ⋸ ELEMENT OF WITH UNDERBAR +22F9: ⋹ ELEMENT OF WITH TWO HORIZONTAL STROKES +22FA: ⋺ CONTAINS WITH LONG HORIZONTAL STROKE +22FB: ⋻ CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE +22FC: ⋼ SMALL CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE +22FD: ⋽ CONTAINS WITH OVERBAR +22FE: ⋾ SMALL CONTAINS WITH OVERBAR +22FF: ⋿ Z NOTATION BAG MEMBERSHIP + +FE33: ︳ PRESENTATION FORM FOR VERTICAL LOW LINE +FFFD: � REPLACEMENT CHARACTER diff --git a/source/tools/openlogsfolder/OpenLogsFolder.vbs b/source/tools/openlogsfolder/OpenLogsFolder.vbs index aae0b39ec8..c2ba6bfdc0 100644 --- a/source/tools/openlogsfolder/OpenLogsFolder.vbs +++ b/source/tools/openlogsfolder/OpenLogsFolder.vbs @@ -1,5 +1,5 @@ -' Simple VBScript to open the 0 A.D. logs folder -' the path varies on different versions of Windows -Set objShell = CreateObject("Shell.Application") -' 0x1C is equivalent to the ssfLOCALAPPDATA constant in VB -objShell.Explore objShell.Namespace(&H1C&).Self.Path & "\0ad\logs" +' Simple VBScript to open the 0 A.D. logs folder +' the path varies on different versions of Windows +Set objShell = CreateObject("Shell.Application") +' 0x1C is equivalent to the ssfLOCALAPPDATA constant in VB +objShell.Explore objShell.Namespace(&H1C&).Self.Path & "\0ad\logs" diff --git a/source/tools/profiler2/Profiler2Report.js b/source/tools/profiler2/Profiler2Report.js index cb31446732..e829189876 100644 --- a/source/tools/profiler2/Profiler2Report.js +++ b/source/tools/profiler2/Profiler2Report.js @@ -1,426 +1,426 @@ -// Copyright (c) 2016 Wildfire Games -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -// Profiler2Report module -// Create one instance per profiler report you wish to open. -// This gives you the interface to access the raw and processed data - -var Profiler2Report = function(callback, tryLive, file) -{ -var outInterface = {}; - -// Item types returned by the engine -var ITEM_EVENT = 1; -var ITEM_ENTER = 2; -var ITEM_LEAVE = 3; -var ITEM_ATTRIBUTE = 4; - -var g_used_colours = {}; - -var g_raw_data; -var g_data; - -function refresh(callback, tryLive, file) -{ - if (tryLive) - refresh_live(callback, file); - else - refresh_jsonp(callback, file); -} -outInterface.refresh = refresh; - -function refresh_jsonp(callback, source) -{ - if (!source) - { - callback(false); - return - } - var reader = new FileReader(); - reader.onload = function(e) - { - refresh_from_jsonp(callback, e.target.result); - } - reader.onerror = function(e) { - alert("Failed to load report file"); - callback(false); - return; - } - reader.readAsText(source); -} - -function refresh_from_jsonp(callback, content) -{ - var script = document.createElement('script'); - - window.profileDataCB = function(data) - { - script.parentNode.removeChild(script); - - var threads = []; - data.threads.forEach(function(thread) { - var canvas = $(''); - threads.push({'name': thread.name, 'data': { 'events': concat_events(thread.data) }, 'canvas': canvas.get(0)}); - }); - g_raw_data = { 'threads': threads }; - compute_data(); - callback(true); - }; - - script.innerHTML = content; - document.body.appendChild(script); -} - -function refresh_live(callback, file) -{ - $.ajax({ - url: 'http://127.0.0.1:8000/overview', - dataType: 'json', - success: function (data) { - var threads = []; - data.threads.forEach(function(thread) { - threads.push({'name': thread.name}); - }); - var callback_data = { 'threads': threads, 'completed': 0 }; - - threads.forEach(function(thread) { - refresh_thread(callback, thread, callback_data); - }); - }, - error: function (jqXHR, textStatus, errorThrown) - { - console.log('Failed to connect to server ("'+textStatus+'")'); - callback(false); - } - }); -} - -function refresh_thread(callback, thread, callback_data) -{ - $.ajax({ - url: 'http://127.0.0.1:8000/query', - dataType: 'json', - data: { 'thread': thread.name }, - success: function (data) { - data.events = concat_events(data); - thread.data = data; - - if (++callback_data.completed == callback_data.threads.length) - { - g_raw_data = { 'threads': callback_data.threads }; - compute_data(); - callback(true); - } - }, - error: function (jqXHR, textStatus, errorThrown) { - alert('Failed to connect to server ("'+textStatus+'")'); - } - }); -} - -function compute_data(range) -{ - g_data = { "threads" : [] }; - g_data_by_frame = { "threads" : [] }; - for (let thread = 0; thread < g_raw_data.threads.length; thread++) - { - let processed_data = process_raw_data(g_raw_data.threads[thread].data.events, range ); - if (!processed_data.intervals.length && !processed_data.events.length) - continue; - - g_data.threads[thread] = processed_data; - - g_data.threads[thread].intervals_by_type_frame = {}; - - if (!g_data.threads[thread].frames.length) - continue - // compute intervals by types and frames if there are frames. - for (let type in g_data.threads[thread].intervals_by_type) - { - let current_frame = 0; - g_data.threads[thread].intervals_by_type_frame[type] = [[]]; - for (let i = 0; i < g_data.threads[thread].intervals_by_type[type].length;i++) - { - let event = g_data.threads[thread].intervals[g_data.threads[thread].intervals_by_type[type][i]]; - while (current_frame < g_data.threads[thread].frames.length && event.t0 > g_data.threads[thread].frames[current_frame].t1) - { - g_data.threads[thread].intervals_by_type_frame[type].push([]); - current_frame++; - } - if (current_frame < g_data.threads[thread].frames.length) - g_data.threads[thread].intervals_by_type_frame[type][current_frame].push(g_data.threads[thread].intervals_by_type[type][i]); - } - } - }; -} - -function process_raw_data(data, range) -{ - if (!data.length) - return { 'frames': [], 'events': [], 'intervals': [], 'intervals_by_type' : {}, 'tmin': 0, 'tmax': 0 }; - - var start, end; - var tmin, tmax; - - var frames = []; - var last_frame_time_start = undefined; - var last_frame_time_end = undefined; - - var stack = []; - for (var i = 0; i < data.length; ++i) - { - if (data[i][0] == ITEM_EVENT && data[i][2] == '__framestart') - { - if (last_frame_time_end) - frames.push({'t0': last_frame_time_start, 't1': last_frame_time_end}); - last_frame_time_start = data[i][1]; - } - if (data[i][0] == ITEM_ENTER) - stack.push(data[i][2]); - if (data[i][0] == ITEM_LEAVE) - { - if (stack[stack.length-1] == 'frame') - last_frame_time_end = data[i][1]; - stack.pop(); - } - } - if(!range) - { - range = { "tmin" : data[0][1], "tmax" : data[data.length-1][1] }; - } - if (range.numframes) - { - for (var i = data.length - 1; i > 0; --i) - { - if (data[i][0] == ITEM_EVENT && data[i][2] == '__framestart') - { - end = i; - break; - } - } - - var framesfound = 0; - for (var i = end - 1; i > 0; --i) - { - if (data[i][0] == ITEM_EVENT && data[i][2] == '__framestart') - { - start = i; - if (++framesfound == range.numframes) - break; - } - } - - tmin = data[start][1]; - tmax = data[end][1]; - } - else if (range.seconds) - { - var end = data.length - 1; - for (var i = end; i > 0; --i) - { - var type = data[i][0]; - if (type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE) - { - tmax = data[i][1]; - break; - } - } - tmin = tmax - range.seconds; - - for (var i = end; i > 0; --i) - { - var type = data[i][0]; - if ((type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE) && data[i][1] < tmin) - break; - start = i; - } - } - else - { - start = 0; - end = data.length - 1; - tmin = range.tmin; - tmax = range.tmax; - - for (var i = data.length-1; i > 0; --i) - { - var type = data[i][0]; - if ((type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE) && data[i][1] < tmax) - { - end = i; - break; - } - } - - for (var i = end; i > 0; --i) - { - var type = data[i][0]; - if ((type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE) && data[i][1] < tmin) - break; - start = i; - } - - // Move the start/end outwards by another frame, so we don't lose data at the edges - while (start > 0) - { - --start; - if (data[start][0] == ITEM_EVENT && data[start][2] == '__framestart') - break; - } - while (end < data.length-1) - { - ++end; - if (data[end][0] == ITEM_EVENT && data[end][2] == '__framestart') - break; - } - } - - var num_colours = 0; - - var events = []; - - // Read events for the entire data period (not just start..end) - var lastWasEvent = false; - for (var i = 0; i < data.length; ++i) - { - if (data[i][0] == ITEM_EVENT) - { - events.push({'t': data[i][1], 'id': data[i][2]}); - lastWasEvent = true; - } - else if (data[i][0] == ITEM_ATTRIBUTE) - { - if (lastWasEvent) - { - if (!events[events.length-1].attrs) - events[events.length-1].attrs = []; - events[events.length-1].attrs.push(data[i][1]); - } - } - else - { - lastWasEvent = false; - } - } - - - var intervals = []; - var intervals_by_type = {}; - - // Read intervals from the focused data period (start..end) - stack = []; - var lastT = 0; - var lastWasEvent = false; - - for (var i = start; i <= end; ++i) - { - if (data[i][0] == ITEM_EVENT) - { -// if (data[i][1] < lastT) -// console.log('Time went backwards: ' + (data[i][1] - lastT)); - - lastT = data[i][1]; - lastWasEvent = true; - } - else if (data[i][0] == ITEM_ENTER) - { -// if (data[i][1] < lastT) -// console.log('Time - ENTER went backwards: ' + (data[i][1] - lastT) + " - " + JSON.stringify(data[i])); - - stack.push({'t0': data[i][1], 'id': data[i][2]}); - - lastT = data[i][1]; - lastWasEvent = false; - } - else if (data[i][0] == ITEM_LEAVE) - { -// if (data[i][1] < lastT) -// console.log('Time - LEAVE went backwards: ' + (data[i][1] - lastT) + " - " + JSON.stringify(data[i])); - - lastT = data[i][1]; - lastWasEvent = false; - - if (!stack.length) - continue; - - var interval = stack.pop(); - - if (!g_used_colours[interval.id]) - g_used_colours[interval.id] = new_colour(num_colours++); - - interval.colour = g_used_colours[interval.id]; - - interval.t1 = data[i][1]; - interval.duration = interval.t1 - interval.t0; - interval.depth = stack.length; - //console.log(JSON.stringify(interval)); - intervals.push(interval); - if (interval.id in intervals_by_type) - intervals_by_type[interval.id].push(intervals.length-1); - else - intervals_by_type[interval.id] = [intervals.length-1]; - - if (interval.id == "Script" && interval.attrs && interval.attrs.length) - { - let curT = interval.t0; - for (let subItem in interval.attrs) - { - let sub = interval.attrs[subItem]; - if (sub.search("buffer") != -1) - continue; - let newInterv = {}; - newInterv.t0 = curT; - newInterv.duration = +sub.replace(/.+? ([.0-9]+)us/, "$1")/1000000; - if (!newInterv.duration) - continue; - newInterv.t1 = curT + newInterv.duration; - curT += newInterv.duration; - newInterv.id = "Script:" + sub.replace(/(.+?) ([.0-9]+)us/, "$1"); - newInterv.colour = g_used_colours[interval.id]; - newInterv.depth = interval.depth+1; - intervals.push(newInterv); - if (newInterv.id in intervals_by_type) - intervals_by_type[newInterv.id].push(intervals.length-1); - else - intervals_by_type[newInterv.id] = [intervals.length-1]; - } - } - } - else if (data[i][0] == ITEM_ATTRIBUTE) - { - if (!lastWasEvent && stack.length) - { - if (!stack[stack.length-1].attrs) - stack[stack.length-1].attrs = []; - stack[stack.length-1].attrs.push(data[i][1]); - } - } - } - return { 'frames': frames, 'events': events, 'intervals': intervals, 'intervals_by_type' : intervals_by_type, 'tmin': tmin, 'tmax': tmax }; -} - -outInterface.data = function() { return g_data; }; -outInterface.raw_data = function() { return g_raw_data; }; -outInterface.data_by_frame = function() { return g_data_by_frame; }; - -refresh(callback, tryLive, file); - -return outInterface; -}; +// Copyright (c) 2016 Wildfire Games +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Profiler2Report module +// Create one instance per profiler report you wish to open. +// This gives you the interface to access the raw and processed data + +var Profiler2Report = function(callback, tryLive, file) +{ +var outInterface = {}; + +// Item types returned by the engine +var ITEM_EVENT = 1; +var ITEM_ENTER = 2; +var ITEM_LEAVE = 3; +var ITEM_ATTRIBUTE = 4; + +var g_used_colours = {}; + +var g_raw_data; +var g_data; + +function refresh(callback, tryLive, file) +{ + if (tryLive) + refresh_live(callback, file); + else + refresh_jsonp(callback, file); +} +outInterface.refresh = refresh; + +function refresh_jsonp(callback, source) +{ + if (!source) + { + callback(false); + return + } + var reader = new FileReader(); + reader.onload = function(e) + { + refresh_from_jsonp(callback, e.target.result); + } + reader.onerror = function(e) { + alert("Failed to load report file"); + callback(false); + return; + } + reader.readAsText(source); +} + +function refresh_from_jsonp(callback, content) +{ + var script = document.createElement('script'); + + window.profileDataCB = function(data) + { + script.parentNode.removeChild(script); + + var threads = []; + data.threads.forEach(function(thread) { + var canvas = $(''); + threads.push({'name': thread.name, 'data': { 'events': concat_events(thread.data) }, 'canvas': canvas.get(0)}); + }); + g_raw_data = { 'threads': threads }; + compute_data(); + callback(true); + }; + + script.innerHTML = content; + document.body.appendChild(script); +} + +function refresh_live(callback, file) +{ + $.ajax({ + url: 'http://127.0.0.1:8000/overview', + dataType: 'json', + success: function (data) { + var threads = []; + data.threads.forEach(function(thread) { + threads.push({'name': thread.name}); + }); + var callback_data = { 'threads': threads, 'completed': 0 }; + + threads.forEach(function(thread) { + refresh_thread(callback, thread, callback_data); + }); + }, + error: function (jqXHR, textStatus, errorThrown) + { + console.log('Failed to connect to server ("'+textStatus+'")'); + callback(false); + } + }); +} + +function refresh_thread(callback, thread, callback_data) +{ + $.ajax({ + url: 'http://127.0.0.1:8000/query', + dataType: 'json', + data: { 'thread': thread.name }, + success: function (data) { + data.events = concat_events(data); + thread.data = data; + + if (++callback_data.completed == callback_data.threads.length) + { + g_raw_data = { 'threads': callback_data.threads }; + compute_data(); + callback(true); + } + }, + error: function (jqXHR, textStatus, errorThrown) { + alert('Failed to connect to server ("'+textStatus+'")'); + } + }); +} + +function compute_data(range) +{ + g_data = { "threads" : [] }; + g_data_by_frame = { "threads" : [] }; + for (let thread = 0; thread < g_raw_data.threads.length; thread++) + { + let processed_data = process_raw_data(g_raw_data.threads[thread].data.events, range ); + if (!processed_data.intervals.length && !processed_data.events.length) + continue; + + g_data.threads[thread] = processed_data; + + g_data.threads[thread].intervals_by_type_frame = {}; + + if (!g_data.threads[thread].frames.length) + continue + // compute intervals by types and frames if there are frames. + for (let type in g_data.threads[thread].intervals_by_type) + { + let current_frame = 0; + g_data.threads[thread].intervals_by_type_frame[type] = [[]]; + for (let i = 0; i < g_data.threads[thread].intervals_by_type[type].length;i++) + { + let event = g_data.threads[thread].intervals[g_data.threads[thread].intervals_by_type[type][i]]; + while (current_frame < g_data.threads[thread].frames.length && event.t0 > g_data.threads[thread].frames[current_frame].t1) + { + g_data.threads[thread].intervals_by_type_frame[type].push([]); + current_frame++; + } + if (current_frame < g_data.threads[thread].frames.length) + g_data.threads[thread].intervals_by_type_frame[type][current_frame].push(g_data.threads[thread].intervals_by_type[type][i]); + } + } + }; +} + +function process_raw_data(data, range) +{ + if (!data.length) + return { 'frames': [], 'events': [], 'intervals': [], 'intervals_by_type' : {}, 'tmin': 0, 'tmax': 0 }; + + var start, end; + var tmin, tmax; + + var frames = []; + var last_frame_time_start = undefined; + var last_frame_time_end = undefined; + + var stack = []; + for (var i = 0; i < data.length; ++i) + { + if (data[i][0] == ITEM_EVENT && data[i][2] == '__framestart') + { + if (last_frame_time_end) + frames.push({'t0': last_frame_time_start, 't1': last_frame_time_end}); + last_frame_time_start = data[i][1]; + } + if (data[i][0] == ITEM_ENTER) + stack.push(data[i][2]); + if (data[i][0] == ITEM_LEAVE) + { + if (stack[stack.length-1] == 'frame') + last_frame_time_end = data[i][1]; + stack.pop(); + } + } + if(!range) + { + range = { "tmin" : data[0][1], "tmax" : data[data.length-1][1] }; + } + if (range.numframes) + { + for (var i = data.length - 1; i > 0; --i) + { + if (data[i][0] == ITEM_EVENT && data[i][2] == '__framestart') + { + end = i; + break; + } + } + + var framesfound = 0; + for (var i = end - 1; i > 0; --i) + { + if (data[i][0] == ITEM_EVENT && data[i][2] == '__framestart') + { + start = i; + if (++framesfound == range.numframes) + break; + } + } + + tmin = data[start][1]; + tmax = data[end][1]; + } + else if (range.seconds) + { + var end = data.length - 1; + for (var i = end; i > 0; --i) + { + var type = data[i][0]; + if (type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE) + { + tmax = data[i][1]; + break; + } + } + tmin = tmax - range.seconds; + + for (var i = end; i > 0; --i) + { + var type = data[i][0]; + if ((type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE) && data[i][1] < tmin) + break; + start = i; + } + } + else + { + start = 0; + end = data.length - 1; + tmin = range.tmin; + tmax = range.tmax; + + for (var i = data.length-1; i > 0; --i) + { + var type = data[i][0]; + if ((type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE) && data[i][1] < tmax) + { + end = i; + break; + } + } + + for (var i = end; i > 0; --i) + { + var type = data[i][0]; + if ((type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE) && data[i][1] < tmin) + break; + start = i; + } + + // Move the start/end outwards by another frame, so we don't lose data at the edges + while (start > 0) + { + --start; + if (data[start][0] == ITEM_EVENT && data[start][2] == '__framestart') + break; + } + while (end < data.length-1) + { + ++end; + if (data[end][0] == ITEM_EVENT && data[end][2] == '__framestart') + break; + } + } + + var num_colours = 0; + + var events = []; + + // Read events for the entire data period (not just start..end) + var lastWasEvent = false; + for (var i = 0; i < data.length; ++i) + { + if (data[i][0] == ITEM_EVENT) + { + events.push({'t': data[i][1], 'id': data[i][2]}); + lastWasEvent = true; + } + else if (data[i][0] == ITEM_ATTRIBUTE) + { + if (lastWasEvent) + { + if (!events[events.length-1].attrs) + events[events.length-1].attrs = []; + events[events.length-1].attrs.push(data[i][1]); + } + } + else + { + lastWasEvent = false; + } + } + + + var intervals = []; + var intervals_by_type = {}; + + // Read intervals from the focused data period (start..end) + stack = []; + var lastT = 0; + var lastWasEvent = false; + + for (var i = start; i <= end; ++i) + { + if (data[i][0] == ITEM_EVENT) + { +// if (data[i][1] < lastT) +// console.log('Time went backwards: ' + (data[i][1] - lastT)); + + lastT = data[i][1]; + lastWasEvent = true; + } + else if (data[i][0] == ITEM_ENTER) + { +// if (data[i][1] < lastT) +// console.log('Time - ENTER went backwards: ' + (data[i][1] - lastT) + " - " + JSON.stringify(data[i])); + + stack.push({'t0': data[i][1], 'id': data[i][2]}); + + lastT = data[i][1]; + lastWasEvent = false; + } + else if (data[i][0] == ITEM_LEAVE) + { +// if (data[i][1] < lastT) +// console.log('Time - LEAVE went backwards: ' + (data[i][1] - lastT) + " - " + JSON.stringify(data[i])); + + lastT = data[i][1]; + lastWasEvent = false; + + if (!stack.length) + continue; + + var interval = stack.pop(); + + if (!g_used_colours[interval.id]) + g_used_colours[interval.id] = new_colour(num_colours++); + + interval.colour = g_used_colours[interval.id]; + + interval.t1 = data[i][1]; + interval.duration = interval.t1 - interval.t0; + interval.depth = stack.length; + //console.log(JSON.stringify(interval)); + intervals.push(interval); + if (interval.id in intervals_by_type) + intervals_by_type[interval.id].push(intervals.length-1); + else + intervals_by_type[interval.id] = [intervals.length-1]; + + if (interval.id == "Script" && interval.attrs && interval.attrs.length) + { + let curT = interval.t0; + for (let subItem in interval.attrs) + { + let sub = interval.attrs[subItem]; + if (sub.search("buffer") != -1) + continue; + let newInterv = {}; + newInterv.t0 = curT; + newInterv.duration = +sub.replace(/.+? ([.0-9]+)us/, "$1")/1000000; + if (!newInterv.duration) + continue; + newInterv.t1 = curT + newInterv.duration; + curT += newInterv.duration; + newInterv.id = "Script:" + sub.replace(/(.+?) ([.0-9]+)us/, "$1"); + newInterv.colour = g_used_colours[interval.id]; + newInterv.depth = interval.depth+1; + intervals.push(newInterv); + if (newInterv.id in intervals_by_type) + intervals_by_type[newInterv.id].push(intervals.length-1); + else + intervals_by_type[newInterv.id] = [intervals.length-1]; + } + } + } + else if (data[i][0] == ITEM_ATTRIBUTE) + { + if (!lastWasEvent && stack.length) + { + if (!stack[stack.length-1].attrs) + stack[stack.length-1].attrs = []; + stack[stack.length-1].attrs.push(data[i][1]); + } + } + } + return { 'frames': frames, 'events': events, 'intervals': intervals, 'intervals_by_type' : intervals_by_type, 'tmin': tmin, 'tmax': tmax }; +} + +outInterface.data = function() { return g_data; }; +outInterface.raw_data = function() { return g_raw_data; }; +outInterface.data_by_frame = function() { return g_data_by_frame; }; + +refresh(callback, tryLive, file); + +return outInterface; +}; diff --git a/source/tools/profiler2/ReportDraw.js b/source/tools/profiler2/ReportDraw.js index 6a0168118e..2239edc032 100644 --- a/source/tools/profiler2/ReportDraw.js +++ b/source/tools/profiler2/ReportDraw.js @@ -1,479 +1,479 @@ -// Copyright (c) 2016 Wildfire Games -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -// Handles the drawing of a report - -var g_report_draw = (function() -{ -var outInterface = {}; - -var mouse_is_down = null; - -function rebuild_canvases(raw_data) -{ - g_canvas = {}; - - g_canvas.canvas_frames = $('').get(0); - g_canvas.threads = {}; - - for (var thread = 0; thread < raw_data.threads.length; thread++) - g_canvas.threads[thread] = $('').get(0); - - g_canvas.canvas_zoom = $('').get(0); - g_canvas.text_output = $('
').get(0);
-    
-    $('#timelines').empty();
-        $('#timelines').append("

Main thread frames

"); - $('#timelines').append(g_canvas.canvas_frames); - for (var thread = 0; thread < raw_data.threads.length; thread++) - { - $('#timelines').append("

" + raw_data.threads[thread].name + "

"); - $('#timelines').append($(g_canvas.threads[thread])); - } - - $('#timelines').append("

Zoomed frames

"); - $('#timelines').append(g_canvas.canvas_zoom); - $('#timelines').append(g_canvas.text_output); -} - -function update_display(report, range) -{ - let data = report.data(); - let raw_data = report.raw_data(); - let main_data = data.threads[g_main_thread]; - - rebuild_canvases(raw_data); - - if (range.seconds) - { - range.tmax = main_data.frames[main_data.frames.length-1].t1; - range.tmin = main_data.frames[main_data.frames.length-1].t1-range.seconds; - } - else if (range.frames) - { - range.tmax = main_data.frames[main_data.frames.length-1].t1; - range.tmin = main_data.frames[main_data.frames.length-1-range.frames].t0; - } - - $(g_canvas.text_output).empty(); - - display_frames(data.threads[g_main_thread], g_canvas.canvas_frames, range); - display_events(data.threads[g_main_thread], g_canvas.canvas_frames, range); - - set_frames_zoom_handlers(report, g_canvas.canvas_frames); - set_tooltip_handlers(g_canvas.canvas_frames); - - $(g_canvas.canvas_zoom).unbind(); - - set_zoom_handlers(data.threads[g_main_thread], data.threads[g_main_thread], g_canvas.threads[g_main_thread], g_canvas.canvas_zoom); - set_tooltip_handlers(data.canvas_zoom); - - for (var i = 0; i < data.threads.length; i++) - { - $(g_canvas.threads[i]).unbind(); - - let events = slice_intervals(data.threads[i], range); - - display_hierarchy(data.threads[i], events, g_canvas.threads[i], {}); - set_zoom_handlers(data.threads[i], events, g_canvas.threads[i], g_canvas.canvas_zoom); - set_tooltip_handlers(g_canvas.threads[i]); - }; -} -outInterface.update_display = update_display; - -function display_frames(data, canvas, range) -{ - canvas._tooltips = []; - - var ctx = canvas.getContext('2d'); - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.save(); - - var xpadding = 8; - var padding_top = 40; - var width = canvas.width - xpadding*2; - var height = canvas.height - padding_top - 4; - - var tmin = data.tmin; - var tmax = data.tmax; - var dx = width / (tmax-tmin); - - canvas._zoomData = { - 'x_to_t': x => tmin + (x - xpadding) / dx, - 't_to_x': t => (t - tmin) * dx + xpadding - }; - - // log 100 scale, skip < 15 ms (60fps) - var scale = x => 1 - Math.max(0, Math.log(1 + (x-15)/10) / Math.log(100)); - - ctx.strokeStyle = 'rgb(0, 0, 0)'; - ctx.fillStyle = 'rgb(255, 255, 255)'; - for (var i = 0; i < data.frames.length; ++i) - { - var frame = data.frames[i]; - - var duration = frame.t1 - frame.t0; - var x0 = xpadding + dx*(frame.t0 - tmin); - var x1 = x0 + dx*duration; - var y1 = canvas.height; - var y0 = y1 * scale(duration*1000); - - ctx.beginPath(); - ctx.rect(x0, y0, x1-x0, y1-y0); - ctx.stroke(); - - canvas._tooltips.push({ - 'x0': x0, 'x1': x1, - 'y0': y0, 'y1': y1, - 'text': function(frame, duration) { return function() { - var t = 'Frame
'; - t += 'Length: ' + time_label(duration) + '
'; - if (frame.attrs) - { - frame.attrs.forEach(function(attr) - { - t += attr + '
'; - }); - } - return t; - }} (frame, duration) - }); - } - - [16, 33, 200, 500].forEach(function(t) - { - var y1 = canvas.height; - var y0 = y1 * scale(t); - var y = Math.floor(y0) + 0.5; - - ctx.beginPath(); - ctx.moveTo(xpadding, y); - ctx.lineTo(canvas.width - xpadding, y); - ctx.strokeStyle = 'rgb(255, 0, 0)'; - ctx.stroke(); - ctx.fillStyle = 'rgb(255, 0, 0)'; - ctx.fillText(t+'ms', 0, y-2); - }); - - ctx.strokeStyle = 'rgba(0, 0, 255, 0.5)'; - ctx.fillStyle = 'rgba(128, 128, 255, 0.2)'; - ctx.beginPath(); - ctx.rect(xpadding + dx*(range.tmin - tmin), 0, dx*(range.tmax - range.tmin), canvas.height); - ctx.fill(); - ctx.stroke(); - - ctx.restore(); -} -outInterface.display_frames = display_frames; - -function display_events(data, canvas) -{ - var ctx = canvas.getContext('2d'); - ctx.save(); - - var x_to_time = canvas._zoomData.x_to_t; - var time_to_x = canvas._zoomData.t_to_x; - - for (var i = 0; i < data.events.length; ++i) - { - var event = data.events[i]; - - if (event.id == '__framestart') - continue; - - if (event.id == 'gui event' && event.attrs && event.attrs[0] == 'type: mousemove') - continue; - - var x = time_to_x(event.t); - var y = 32; - - if (x < 2) - continue; - - var x0 = x; - var x1 = x; - var y0 = y-4; - var y1 = y+4; - - ctx.strokeStyle = 'rgb(255, 0, 0)'; - ctx.beginPath(); - ctx.moveTo(x0, y0); - ctx.lineTo(x1, y1); - ctx.stroke(); - canvas._tooltips.push({ - 'x0': x0, 'x1': x1, - 'y0': y0, 'y1': y1, - 'text': function(event) { return function() { - var t = '' + event.id + '
'; - if (event.attrs) - { - event.attrs.forEach(function(attr) { - t += attr + '
'; - }); - } - return t; - }} (event) - }); - } - - ctx.restore(); -} -outInterface.display_events = display_events; - -function display_hierarchy(main_data, data, canvas, range, zoom) -{ - canvas._tooltips = []; - - var ctx = canvas.getContext('2d'); - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.save(); - - ctx.font = '12px sans-serif'; - - var xpadding = 8; - var padding_top = 40; - var width = canvas.width - xpadding*2; - var height = canvas.height - padding_top - 4; - - var tmin, tmax, start, end; - - if (range.tmin) - { - tmin = range.tmin; - tmax = range.tmax; - } - else - { - tmin = data.tmin; - tmax = data.tmax; - } - - canvas._hierarchyData = { 'range': range, 'tmin': tmin, 'tmax': tmax }; - - function time_to_x(t) - { - return xpadding + (t - tmin) / (tmax - tmin) * width; - } - - function x_to_time(x) - { - return tmin + (x - xpadding) * (tmax - tmin) / width; - } - - ctx.save(); - ctx.textAlign = 'center'; - ctx.strokeStyle = 'rgb(192, 192, 192)'; - ctx.beginPath(); - var precision = -3; - while ((tmax-tmin)*Math.pow(10, 3+precision) < 25) - ++precision; - if (precision > 10) - precision = 10; - if (precision < 0) - precision = 0; - var ticks_per_sec = Math.pow(10, 3+precision); - var major_tick_interval = 5; - - for (var i = 0; i < (tmax-tmin)*ticks_per_sec; ++i) - { - var major = (i % major_tick_interval == 0); - var x = Math.floor(time_to_x(tmin + i/ticks_per_sec)); - ctx.moveTo(x-0.5, padding_top - (major ? 4 : 2)); - ctx.lineTo(x-0.5, padding_top + height); - if (major) - ctx.fillText((i*1000/ticks_per_sec).toFixed(precision), x, padding_top - 8); - } - ctx.stroke(); - ctx.restore(); - - var BAR_SPACING = 16; - - for (var i = 0; i < data.intervals.length; ++i) - { - var interval = data.intervals[i]; - - if (interval.tmax <= tmin || interval.tmin > tmax) - continue; - - var x0 = Math.floor(time_to_x(interval.t0)); - var x1 = Math.floor(time_to_x(interval.t1)); - - if (x1-x0 < 1) - continue; - - var y0 = padding_top + interval.depth * BAR_SPACING; - var y1 = y0 + BAR_SPACING; - - var label = interval.id; - if (interval.attrs) - { - if (/^\d+$/.exec(interval.attrs[0])) - label += ' ' + interval.attrs[0]; - else - label += ' [...]'; - } - - ctx.fillStyle = interval.colour; - ctx.strokeStyle = 'black'; - ctx.beginPath(); - ctx.rect(x0-0.5, y0-0.5, x1-x0, y1-y0); - ctx.fill(); - ctx.stroke(); - ctx.fillStyle = 'black'; - ctx.fillText(label, x0+2, y0+BAR_SPACING-4, Math.max(1, x1-x0-4)); - - canvas._tooltips.push({ - 'x0': x0, 'x1': x1, - 'y0': y0, 'y1': y1, - 'text': function(interval) { return function() { - var t = '' + interval.id + '
'; - t += 'Length: ' + time_label(interval.duration) + '
'; - if (interval.attrs) - { - interval.attrs.forEach(function(attr) { - t += attr + '
'; - }); - } - return t; - }} (interval) - }); - - } - - for (var i = 0; i < main_data.frames.length; ++i) - { - var frame = main_data.frames[i]; - - if (frame.t0 < tmin || frame.t0 > tmax) - continue; - - var x = Math.floor(time_to_x(frame.t0)); - - ctx.save(); - ctx.lineWidth = 3; - ctx.strokeStyle = 'rgba(0, 0, 255, 0.5)'; - ctx.beginPath(); - ctx.moveTo(x+0.5, 0); - ctx.lineTo(x+0.5, canvas.height); - ctx.stroke(); - ctx.fillText(((frame.t1 - frame.t0) * 1000).toFixed(0)+'ms', x+2, padding_top - 24); - ctx.restore(); - } - - if (zoom) - { - var x0 = time_to_x(zoom.tmin); - var x1 = time_to_x(zoom.tmax); - ctx.strokeStyle = 'rgba(0, 0, 255, 0.5)'; - ctx.fillStyle = 'rgba(128, 128, 255, 0.2)'; - ctx.beginPath(); - ctx.moveTo(x0+0.5, 0.5); - ctx.lineTo(x1+0.5, 0.5); - ctx.lineTo(x1+0.5 + 4, canvas.height-0.5); - ctx.lineTo(x0+0.5 - 4, canvas.height-0.5); - ctx.closePath(); - ctx.fill(); - ctx.stroke(); - } - - ctx.restore(); -} -outInterface.display_hierarchy = display_hierarchy; - -function set_frames_zoom_handlers(report, canvas0) -{ - function do_zoom(report, event) - { - var zdata = canvas0._zoomData; - - var relativeX = event.pageX - this.offsetLeft; - var relativeY = event.pageY - this.offsetTop; - - var width = relativeY / canvas0.height; - width = width*width; - width *= zdata.x_to_t(canvas0.width)/10; - - var tavg = zdata.x_to_t(relativeX); - var tmax = tavg + width/2; - var tmin = tavg - width/2; - var range = {'tmin': tmin, 'tmax': tmax}; - update_display(report, range); - } - - $(canvas0).unbind(); - $(canvas0).mousedown(function(event) - { - mouse_is_down = canvas0; - do_zoom.call(this, report, event); - }); - $(canvas0).mouseup(function(event) - { - mouse_is_down = null; - }); - $(canvas0).mousemove(function(event) - { - if (mouse_is_down) - do_zoom.call(this, report, event); - }); -} - -function set_zoom_handlers(main_data, data, canvas0, canvas1) -{ - function do_zoom(event) - { - var hdata = canvas0._hierarchyData; - - function x_to_time(x) - { - return hdata.tmin + x * (hdata.tmax - hdata.tmin) / canvas0.width; - } - - var relativeX = event.pageX - this.offsetLeft; - var relativeY = (event.pageY + this.offsetTop) / canvas0.height; - relativeY = relativeY - 0.5; - relativeY *= 5; - relativeY *= relativeY; - var width = relativeY / canvas0.height; - width = width*width; - width = 3 + width * x_to_time(canvas0.width)/10; - var zoom = { tmin: x_to_time(relativeX-width/2), tmax: x_to_time(relativeX+width/2) }; - display_hierarchy(main_data, data, canvas0, hdata.range, zoom); - display_hierarchy(main_data, data, canvas1, zoom, undefined); - set_tooltip_handlers(canvas1); - } - - $(canvas0).mousedown(function(event) - { - mouse_is_down = canvas0; - do_zoom.call(this, event); - }); - $(canvas0).mouseup(function(event) - { - mouse_is_down = null; - }); - $(canvas0).mousemove(function(event) - { - if (mouse_is_down) - do_zoom.call(this, event); - }); -} - -return outInterface; +// Copyright (c) 2016 Wildfire Games +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Handles the drawing of a report + +var g_report_draw = (function() +{ +var outInterface = {}; + +var mouse_is_down = null; + +function rebuild_canvases(raw_data) +{ + g_canvas = {}; + + g_canvas.canvas_frames = $('').get(0); + g_canvas.threads = {}; + + for (var thread = 0; thread < raw_data.threads.length; thread++) + g_canvas.threads[thread] = $('').get(0); + + g_canvas.canvas_zoom = $('').get(0); + g_canvas.text_output = $('
').get(0);
+    
+    $('#timelines').empty();
+        $('#timelines').append("

Main thread frames

"); + $('#timelines').append(g_canvas.canvas_frames); + for (var thread = 0; thread < raw_data.threads.length; thread++) + { + $('#timelines').append("

" + raw_data.threads[thread].name + "

"); + $('#timelines').append($(g_canvas.threads[thread])); + } + + $('#timelines').append("

Zoomed frames

"); + $('#timelines').append(g_canvas.canvas_zoom); + $('#timelines').append(g_canvas.text_output); +} + +function update_display(report, range) +{ + let data = report.data(); + let raw_data = report.raw_data(); + let main_data = data.threads[g_main_thread]; + + rebuild_canvases(raw_data); + + if (range.seconds) + { + range.tmax = main_data.frames[main_data.frames.length-1].t1; + range.tmin = main_data.frames[main_data.frames.length-1].t1-range.seconds; + } + else if (range.frames) + { + range.tmax = main_data.frames[main_data.frames.length-1].t1; + range.tmin = main_data.frames[main_data.frames.length-1-range.frames].t0; + } + + $(g_canvas.text_output).empty(); + + display_frames(data.threads[g_main_thread], g_canvas.canvas_frames, range); + display_events(data.threads[g_main_thread], g_canvas.canvas_frames, range); + + set_frames_zoom_handlers(report, g_canvas.canvas_frames); + set_tooltip_handlers(g_canvas.canvas_frames); + + $(g_canvas.canvas_zoom).unbind(); + + set_zoom_handlers(data.threads[g_main_thread], data.threads[g_main_thread], g_canvas.threads[g_main_thread], g_canvas.canvas_zoom); + set_tooltip_handlers(data.canvas_zoom); + + for (var i = 0; i < data.threads.length; i++) + { + $(g_canvas.threads[i]).unbind(); + + let events = slice_intervals(data.threads[i], range); + + display_hierarchy(data.threads[i], events, g_canvas.threads[i], {}); + set_zoom_handlers(data.threads[i], events, g_canvas.threads[i], g_canvas.canvas_zoom); + set_tooltip_handlers(g_canvas.threads[i]); + }; +} +outInterface.update_display = update_display; + +function display_frames(data, canvas, range) +{ + canvas._tooltips = []; + + var ctx = canvas.getContext('2d'); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.save(); + + var xpadding = 8; + var padding_top = 40; + var width = canvas.width - xpadding*2; + var height = canvas.height - padding_top - 4; + + var tmin = data.tmin; + var tmax = data.tmax; + var dx = width / (tmax-tmin); + + canvas._zoomData = { + 'x_to_t': x => tmin + (x - xpadding) / dx, + 't_to_x': t => (t - tmin) * dx + xpadding + }; + + // log 100 scale, skip < 15 ms (60fps) + var scale = x => 1 - Math.max(0, Math.log(1 + (x-15)/10) / Math.log(100)); + + ctx.strokeStyle = 'rgb(0, 0, 0)'; + ctx.fillStyle = 'rgb(255, 255, 255)'; + for (var i = 0; i < data.frames.length; ++i) + { + var frame = data.frames[i]; + + var duration = frame.t1 - frame.t0; + var x0 = xpadding + dx*(frame.t0 - tmin); + var x1 = x0 + dx*duration; + var y1 = canvas.height; + var y0 = y1 * scale(duration*1000); + + ctx.beginPath(); + ctx.rect(x0, y0, x1-x0, y1-y0); + ctx.stroke(); + + canvas._tooltips.push({ + 'x0': x0, 'x1': x1, + 'y0': y0, 'y1': y1, + 'text': function(frame, duration) { return function() { + var t = 'Frame
'; + t += 'Length: ' + time_label(duration) + '
'; + if (frame.attrs) + { + frame.attrs.forEach(function(attr) + { + t += attr + '
'; + }); + } + return t; + }} (frame, duration) + }); + } + + [16, 33, 200, 500].forEach(function(t) + { + var y1 = canvas.height; + var y0 = y1 * scale(t); + var y = Math.floor(y0) + 0.5; + + ctx.beginPath(); + ctx.moveTo(xpadding, y); + ctx.lineTo(canvas.width - xpadding, y); + ctx.strokeStyle = 'rgb(255, 0, 0)'; + ctx.stroke(); + ctx.fillStyle = 'rgb(255, 0, 0)'; + ctx.fillText(t+'ms', 0, y-2); + }); + + ctx.strokeStyle = 'rgba(0, 0, 255, 0.5)'; + ctx.fillStyle = 'rgba(128, 128, 255, 0.2)'; + ctx.beginPath(); + ctx.rect(xpadding + dx*(range.tmin - tmin), 0, dx*(range.tmax - range.tmin), canvas.height); + ctx.fill(); + ctx.stroke(); + + ctx.restore(); +} +outInterface.display_frames = display_frames; + +function display_events(data, canvas) +{ + var ctx = canvas.getContext('2d'); + ctx.save(); + + var x_to_time = canvas._zoomData.x_to_t; + var time_to_x = canvas._zoomData.t_to_x; + + for (var i = 0; i < data.events.length; ++i) + { + var event = data.events[i]; + + if (event.id == '__framestart') + continue; + + if (event.id == 'gui event' && event.attrs && event.attrs[0] == 'type: mousemove') + continue; + + var x = time_to_x(event.t); + var y = 32; + + if (x < 2) + continue; + + var x0 = x; + var x1 = x; + var y0 = y-4; + var y1 = y+4; + + ctx.strokeStyle = 'rgb(255, 0, 0)'; + ctx.beginPath(); + ctx.moveTo(x0, y0); + ctx.lineTo(x1, y1); + ctx.stroke(); + canvas._tooltips.push({ + 'x0': x0, 'x1': x1, + 'y0': y0, 'y1': y1, + 'text': function(event) { return function() { + var t = '' + event.id + '
'; + if (event.attrs) + { + event.attrs.forEach(function(attr) { + t += attr + '
'; + }); + } + return t; + }} (event) + }); + } + + ctx.restore(); +} +outInterface.display_events = display_events; + +function display_hierarchy(main_data, data, canvas, range, zoom) +{ + canvas._tooltips = []; + + var ctx = canvas.getContext('2d'); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.save(); + + ctx.font = '12px sans-serif'; + + var xpadding = 8; + var padding_top = 40; + var width = canvas.width - xpadding*2; + var height = canvas.height - padding_top - 4; + + var tmin, tmax, start, end; + + if (range.tmin) + { + tmin = range.tmin; + tmax = range.tmax; + } + else + { + tmin = data.tmin; + tmax = data.tmax; + } + + canvas._hierarchyData = { 'range': range, 'tmin': tmin, 'tmax': tmax }; + + function time_to_x(t) + { + return xpadding + (t - tmin) / (tmax - tmin) * width; + } + + function x_to_time(x) + { + return tmin + (x - xpadding) * (tmax - tmin) / width; + } + + ctx.save(); + ctx.textAlign = 'center'; + ctx.strokeStyle = 'rgb(192, 192, 192)'; + ctx.beginPath(); + var precision = -3; + while ((tmax-tmin)*Math.pow(10, 3+precision) < 25) + ++precision; + if (precision > 10) + precision = 10; + if (precision < 0) + precision = 0; + var ticks_per_sec = Math.pow(10, 3+precision); + var major_tick_interval = 5; + + for (var i = 0; i < (tmax-tmin)*ticks_per_sec; ++i) + { + var major = (i % major_tick_interval == 0); + var x = Math.floor(time_to_x(tmin + i/ticks_per_sec)); + ctx.moveTo(x-0.5, padding_top - (major ? 4 : 2)); + ctx.lineTo(x-0.5, padding_top + height); + if (major) + ctx.fillText((i*1000/ticks_per_sec).toFixed(precision), x, padding_top - 8); + } + ctx.stroke(); + ctx.restore(); + + var BAR_SPACING = 16; + + for (var i = 0; i < data.intervals.length; ++i) + { + var interval = data.intervals[i]; + + if (interval.tmax <= tmin || interval.tmin > tmax) + continue; + + var x0 = Math.floor(time_to_x(interval.t0)); + var x1 = Math.floor(time_to_x(interval.t1)); + + if (x1-x0 < 1) + continue; + + var y0 = padding_top + interval.depth * BAR_SPACING; + var y1 = y0 + BAR_SPACING; + + var label = interval.id; + if (interval.attrs) + { + if (/^\d+$/.exec(interval.attrs[0])) + label += ' ' + interval.attrs[0]; + else + label += ' [...]'; + } + + ctx.fillStyle = interval.colour; + ctx.strokeStyle = 'black'; + ctx.beginPath(); + ctx.rect(x0-0.5, y0-0.5, x1-x0, y1-y0); + ctx.fill(); + ctx.stroke(); + ctx.fillStyle = 'black'; + ctx.fillText(label, x0+2, y0+BAR_SPACING-4, Math.max(1, x1-x0-4)); + + canvas._tooltips.push({ + 'x0': x0, 'x1': x1, + 'y0': y0, 'y1': y1, + 'text': function(interval) { return function() { + var t = '' + interval.id + '
'; + t += 'Length: ' + time_label(interval.duration) + '
'; + if (interval.attrs) + { + interval.attrs.forEach(function(attr) { + t += attr + '
'; + }); + } + return t; + }} (interval) + }); + + } + + for (var i = 0; i < main_data.frames.length; ++i) + { + var frame = main_data.frames[i]; + + if (frame.t0 < tmin || frame.t0 > tmax) + continue; + + var x = Math.floor(time_to_x(frame.t0)); + + ctx.save(); + ctx.lineWidth = 3; + ctx.strokeStyle = 'rgba(0, 0, 255, 0.5)'; + ctx.beginPath(); + ctx.moveTo(x+0.5, 0); + ctx.lineTo(x+0.5, canvas.height); + ctx.stroke(); + ctx.fillText(((frame.t1 - frame.t0) * 1000).toFixed(0)+'ms', x+2, padding_top - 24); + ctx.restore(); + } + + if (zoom) + { + var x0 = time_to_x(zoom.tmin); + var x1 = time_to_x(zoom.tmax); + ctx.strokeStyle = 'rgba(0, 0, 255, 0.5)'; + ctx.fillStyle = 'rgba(128, 128, 255, 0.2)'; + ctx.beginPath(); + ctx.moveTo(x0+0.5, 0.5); + ctx.lineTo(x1+0.5, 0.5); + ctx.lineTo(x1+0.5 + 4, canvas.height-0.5); + ctx.lineTo(x0+0.5 - 4, canvas.height-0.5); + ctx.closePath(); + ctx.fill(); + ctx.stroke(); + } + + ctx.restore(); +} +outInterface.display_hierarchy = display_hierarchy; + +function set_frames_zoom_handlers(report, canvas0) +{ + function do_zoom(report, event) + { + var zdata = canvas0._zoomData; + + var relativeX = event.pageX - this.offsetLeft; + var relativeY = event.pageY - this.offsetTop; + + var width = relativeY / canvas0.height; + width = width*width; + width *= zdata.x_to_t(canvas0.width)/10; + + var tavg = zdata.x_to_t(relativeX); + var tmax = tavg + width/2; + var tmin = tavg - width/2; + var range = {'tmin': tmin, 'tmax': tmax}; + update_display(report, range); + } + + $(canvas0).unbind(); + $(canvas0).mousedown(function(event) + { + mouse_is_down = canvas0; + do_zoom.call(this, report, event); + }); + $(canvas0).mouseup(function(event) + { + mouse_is_down = null; + }); + $(canvas0).mousemove(function(event) + { + if (mouse_is_down) + do_zoom.call(this, report, event); + }); +} + +function set_zoom_handlers(main_data, data, canvas0, canvas1) +{ + function do_zoom(event) + { + var hdata = canvas0._hierarchyData; + + function x_to_time(x) + { + return hdata.tmin + x * (hdata.tmax - hdata.tmin) / canvas0.width; + } + + var relativeX = event.pageX - this.offsetLeft; + var relativeY = (event.pageY + this.offsetTop) / canvas0.height; + relativeY = relativeY - 0.5; + relativeY *= 5; + relativeY *= relativeY; + var width = relativeY / canvas0.height; + width = width*width; + width = 3 + width * x_to_time(canvas0.width)/10; + var zoom = { tmin: x_to_time(relativeX-width/2), tmax: x_to_time(relativeX+width/2) }; + display_hierarchy(main_data, data, canvas0, hdata.range, zoom); + display_hierarchy(main_data, data, canvas1, zoom, undefined); + set_tooltip_handlers(canvas1); + } + + $(canvas0).mousedown(function(event) + { + mouse_is_down = canvas0; + do_zoom.call(this, event); + }); + $(canvas0).mouseup(function(event) + { + mouse_is_down = null; + }); + $(canvas0).mousemove(function(event) + { + if (mouse_is_down) + do_zoom.call(this, event); + }); +} + +return outInterface; })(); \ No newline at end of file diff --git a/source/tools/profiler2/profiler2.html b/source/tools/profiler2/profiler2.html index d57cd9412d..cd863f4b0e 100644 --- a/source/tools/profiler2/profiler2.html +++ b/source/tools/profiler2/profiler2.html @@ -1,91 +1,91 @@ - - -0 A.D. profiler UI - - - - - - - - - - -
-

Open reports

-

Use the input field below to load a new report (from JSON)

- - -
- -

Click on the following timelines to zoom.

-
- -
-
-

Analysis

-

Click on any of the event names in "choices" to see more details about them. Load more reports to compare.

-
-
-

Frequency Graph

- -
- -
-
-
-

Frame-by-Frame Graph

- -
- -
-
-
- -
-

Report Comparison

-
-
- - -

-
+
+
+0 A.D. profiler UI
+
+
+
+
+
+
+
+
+
+
+
+

Open reports

+

Use the input field below to load a new report (from JSON)

+ + +
+ +

Click on the following timelines to zoom.

+
+ +
+
+

Analysis

+

Click on any of the event names in "choices" to see more details about them. Load more reports to compare.

+
+
+

Frequency Graph

+ +
+ +
+
+
+

Frame-by-Frame Graph

+ +
+ +
+
+
+ +
+

Report Comparison

+
+
+ + +

+
 
\ No newline at end of file
diff --git a/source/tools/profiler2/profiler2.js b/source/tools/profiler2/profiler2.js
index 02e1980b58..3fc473e4c1 100644
--- a/source/tools/profiler2/profiler2.js
+++ b/source/tools/profiler2/profiler2.js
@@ -1,503 +1,503 @@
-// Copyright (c) 2016 Wildfire Games
-// 
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-// 
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-// 
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-// This file is the main handler, which deals with loading reports and showing the analysis graphs
-// the latter could probably be put in a separate module
-
-// global array of Profiler2Report objects
-var g_reports = [];
-
-var g_main_thread = 0;
-var g_current_report = 0;
-
-var g_profile_path = null;
-var g_active_elements = [];
-var g_loading_timeout = null;
-
-function save_as_file()
-{
-    $.ajax({
-        url: 'http://127.0.0.1:8000/download',
-        success: function () {
-        },
-        error: function (jqXHR, textStatus, errorThrown) {
-        }
-    });
-}
-
-function get_history_data(report, thread, type)
-{
-    var ret = {"time_by_frame":[], "max" : 0, "log_scale" : null};
- 
-    var report_data = g_reports[report].data().threads[thread];
-    var interval_data = report_data.intervals;
-
-    let data = report_data.intervals_by_type_frame[type];
-    if (!data)
-        return ret;
-
-    let max = 0;
-    let avg = [];
-    let current_frame = 0;
-    for (let i = 0; i < data.length; i++)
-    {
-        ret.time_by_frame.push(0);
-        for (let p = 0; p < data[i].length; p++)
-            ret.time_by_frame[ret.time_by_frame.length-1] += interval_data[data[i][p]].duration;
-    }
-
-    // somehow JS sorts 0.03 lower than 3e-7 otherwise
-    let sorted = ret.time_by_frame.slice(0).sort((a,b) => a-b);
-    ret.max = sorted[sorted.length-1];
-    avg = sorted[Math.round(avg.length/2)];
-
-    if (ret.max > avg * 3)
-        ret.log_scale = true;
-
-    return ret;
-}
-
-function draw_frequency_graph()
-{
-    let canvas = document.getElementById("canvas_frequency");
-    canvas._tooltips = [];
-
-    let context = canvas.getContext("2d");
-    context.clearRect(0, 0, canvas.width, canvas.height);
-
-    let legend = document.getElementById("frequency_graph").querySelector("aside");
-    legend.innerHTML = "";
-
-    if (!g_active_elements.length)
-        return;
-
-    var series_data = {};
-    var use_log_scale = null;
-
-    var x_scale = 0;
-    var y_scale = 0;
-    var padding = 10;
-
-    var item_nb = 0;
-
-    var tooltip_helper = {};
-
-    for (let typeI in g_active_elements)
-    {
-        for (let rep in g_reports)
-        {
-            item_nb++;
-            let data = get_history_data(rep, g_main_thread, g_active_elements[typeI]);
-            let name = rep + "/" + g_active_elements[typeI];
-            if (document.getElementById('fulln').checked)
-                series_data[name] = data.time_by_frame.sort((a,b) => a-b);
-            else
-                series_data[name] = data.time_by_frame.filter(a=>a).sort((a,b) => a-b);
-            if (series_data[name].length > x_scale)
-                x_scale = series_data[name].length;
-            if (data.max > y_scale)
-                y_scale = data.max;
-            if (use_log_scale === null && data.log_scale)
-                use_log_scale = true;
-        }
-    }
-    if (use_log_scale)
-    {
-        let legend_item = document.createElement("p");
-        legend_item.style.borderColor = "transparent";
-        legend_item.textContent = " -- log x scale -- ";
-        legend.appendChild(legend_item);
-    }
-    let id = 0;
-    for (let type in series_data)
-    {
-        let colour = graph_colour(id);
-        let time_by_frame = series_data[type];
-        let p = 0;
-        let last_val = 0;
-
-        let nb = document.createElement("p");
-        nb.style.borderColor = colour;
-        nb.textContent = type + " - n=" + time_by_frame.length;
-        legend.appendChild(nb);
-
-        for (var i = 0; i < time_by_frame.length; i++)
-        {
-            let x0 = i/time_by_frame.length*(canvas.width-padding*2) + padding;
-            if (i == 0)
-                x0 = 0;
-            let x1 = (i+1)/time_by_frame.length*(canvas.width-padding*2) + padding;
-            if (i == time_by_frame.length-1)
-                x1 = (time_by_frame.length-1)*canvas.width;
-
-            let y = time_by_frame[i]/y_scale;
-            if (use_log_scale)
-                y = Math.log10(1 + time_by_frame[i]/y_scale * 9);
-
-            context.globalCompositeOperation = "lighter";
-
-            context.beginPath();
-            context.strokeStyle = colour
-            context.lineWidth = 0.5;
-            context.moveTo(x0,canvas.height * (1 - last_val));
-            context.lineTo(x1,canvas.height * (1 - y));
-            context.stroke();
-
-            last_val = y;
-            if (!tooltip_helper[Math.floor(x0)])
-                tooltip_helper[Math.floor(x0)] = [];
-            tooltip_helper[Math.floor(x0)].push([y, type]);
-        }
-        id++;
-    }
-
-    for (let i in tooltip_helper)
-    {
-        let tooltips = tooltip_helper[i];
-        let text = "";
-        for (let j in tooltips)
-            if (tooltips[j][0] != undefined && text.search(tooltips[j][1])===-1)
-                text += "Series " + tooltips[j][1] + ": " + time_label((tooltips[j][0])*y_scale,1) + "
"; - canvas._tooltips.push({ - 'x0': +i, 'x1': +i+1, - 'y0': 0, 'y1': canvas.height, - 'text': function(text) { return function() { return text; } }(text) - }); - } - set_tooltip_handlers(canvas); - - [0.02,0.05,0.1,0.25,0.5,0.75].forEach(function(y_val) - { - let y = y_val; - if (use_log_scale) - y = Math.log10(1 + y_val * 9); - - context.beginPath(); - context.lineWidth="1"; - context.strokeStyle = "rgba(0,0,0,0.2)"; - context.moveTo(0,canvas.height * (1- y)); - context.lineTo(canvas.width,canvas.height * (1 - y)); - context.stroke(); - context.fillStyle = "gray"; - context.font = "10px Arial"; - context.textAlign="left"; - context.fillText(time_label(y*y_scale,0), 2, canvas.height * (1 - y) - 2 ); - }); -} - -function draw_history_graph() -{ - let canvas = document.getElementById("canvas_history"); - canvas._tooltips = []; - - let context = canvas.getContext("2d"); - context.clearRect(0, 0, canvas.width, canvas.height); - - let legend = document.getElementById("history_graph").querySelector("aside"); - legend.innerHTML = ""; - - if (!g_active_elements.length) - return; - - var series_data = {}; - var use_log_scale = null; - - var frames_nb = Infinity; - var x_scale = 0; - var y_scale = 0; - - var item_nb = 0; - - var tooltip_helper = {}; - - for (let typeI in g_active_elements) - { - for (let rep in g_reports) - { - if (g_reports[rep].data().threads[g_main_thread].frames.length < frames_nb) - frames_nb = g_reports[rep].data().threads[g_main_thread].frames.length; - item_nb++; - let data = get_history_data(rep, g_main_thread, g_active_elements[typeI]); - if (!document.getElementById('smooth').value) - series_data[rep + "/" + g_active_elements[typeI]] = data.time_by_frame; - else - series_data[rep + "/" + g_active_elements[typeI]] = smooth_1D_array(data.time_by_frame, +document.getElementById('smooth').value); - if (data.max > y_scale) - y_scale = data.max; - if (use_log_scale === null && data.log_scale) - use_log_scale = true; - } - } - if (use_log_scale) - { - let legend_item = document.createElement("p"); - legend_item.style.borderColor = "transparent"; - legend_item.textContent = " -- log y scale -- "; - legend.appendChild(legend_item); - } - canvas.width = Math.max(frames_nb,600); - x_scale = frames_nb / canvas.width; - let id = 0; - for (let type in series_data) - { - let colour = graph_colour(id); - - let legend_item = document.createElement("p"); - legend_item.style.borderColor = colour; - legend_item.textContent = type; - legend.appendChild(legend_item); - - let time_by_frame = series_data[type]; - let last_val = 0; - for (var i = 0; i < frames_nb; i++) - { - let smoothed_time = time_by_frame[i];//smooth_1D(time_by_frame.slice(0), i, 3); - - let y = smoothed_time/y_scale; - if (use_log_scale) - y = Math.log10(1 + smoothed_time/y_scale * 9); - - if (item_nb === 1) - { - context.beginPath(); - context.fillStyle = colour; - context.fillRect(i/x_scale,canvas.height,1/x_scale,-y*canvas.height); - } - else - { - if ( i == frames_nb-1) - continue; - context.globalCompositeOperation = "lighten"; - context.beginPath(); - context.strokeStyle = colour - context.lineWidth = 0.5; - context.moveTo(i/x_scale,canvas.height * (1 - last_val)); - context.lineTo((i+1)/x_scale,canvas.height * (1 - y)); - context.stroke(); - } - last_val = y; - if (!tooltip_helper[Math.floor(i/x_scale)]) - tooltip_helper[Math.floor(i/x_scale)] = []; - tooltip_helper[Math.floor(i/x_scale)].push([y, type]); - } - id++; - } - - for (let i in tooltip_helper) - { - let tooltips = tooltip_helper[i]; - let text = "Frame " + i*x_scale + "
"; - for (let j in tooltips) - if (tooltips[j][0] != undefined && text.search(tooltips[j][1])===-1) - text += "Series " + tooltips[j][1] + ": " + time_label((tooltips[j][0])*y_scale,1) + "
"; - canvas._tooltips.push({ - 'x0': +i, 'x1': +i+1, - 'y0': 0, 'y1': canvas.height, - 'text': function(text) { return function() { return text; } }(text) - }); - } - set_tooltip_handlers(canvas); - - [0.1,0.25,0.5,0.75].forEach(function(y_val) - { - let y = y_val; - if (use_log_scale) - y = Math.log10(1 + y_val * 9); - - context.beginPath(); - context.lineWidth="1"; - context.strokeStyle = "rgba(0,0,0,0.2)"; - context.moveTo(0,canvas.height * (1- y)); - context.lineTo(canvas.width,canvas.height * (1 - y)); - context.stroke(); - context.fillStyle = "gray"; - context.font = "10px Arial"; - context.textAlign="left"; - context.fillText(time_label(y*y_scale,0), 2, canvas.height * (1 - y) - 2 ); - }); -} - -function compare_reports() -{ - let section = document.getElementById("comparison"); - section.innerHTML = "

Report Comparison

"; - - if (g_reports.length < 2) - { - section.innerHTML += "

Too few reports loaded

"; - return; - } - - if (g_active_elements.length != 1) - { - section.innerHTML += "

Too many of too few elements selected

"; - return; - } - - let frames_nb = g_reports[0].data().threads[g_main_thread].frames.length; - for (let rep in g_reports) - if (g_reports[rep].data().threads[g_main_thread].frames.length < frames_nb) - frames_nb = g_reports[rep].data().threads[g_main_thread].frames.length; - - if (frames_nb != g_reports[0].data().threads[g_main_thread].frames.length) - section.innerHTML += "

Only the first " + frames_nb + " frames will be considered.

"; - - let reports_data = []; - - for (let rep in g_reports) - { - let raw_data = get_history_data(rep, g_main_thread, g_active_elements[0]).time_by_frame; - reports_data.push({"time_data" : raw_data.slice(0,frames_nb), "sorted_data" : raw_data.slice(0,frames_nb).sort((a,b) => a-b)}); - } - - let table_output = "" - for (let rep in reports_data) - { - let report = reports_data[rep]; - table_output += ""; - // median - table_output += "" - // max - table_output += "" - let frames_better = 0; - let frames_diff = 0; - for (let f in report.time_data) - { - if (report.time_data[f] <= reports_data[0].time_data[f]) - frames_better++; - frames_diff += report.time_data[f] - reports_data[0].time_data[f]; - } - table_output += ""; - table_output += ""; - } - section.innerHTML += table_output + "
Profiler VariableMedianMaximum% better framestime difference per frame
Report " + rep + (rep == 0 ? " (reference)":"") + "" + time_label(report.sorted_data[Math.floor(report.sorted_data.length/2)]) + "" + time_label(report.sorted_data[report.sorted_data.length-1]) + "" + (frames_better/frames_nb*100).toFixed(0) + "%" + time_label((frames_diff/frames_nb),2) + "
"; -} - -function recompute_choices(report, thread) -{ - var choices = document.getElementById("choices").querySelector("nav"); - choices.innerHTML = "

Choices

"; - var types = {}; - var data = report.data().threads[thread]; - - for (let i = 0; i < data.intervals.length; i++) - types[data.intervals[i].id] = 0; - - var sorted_keys = Object.keys(types).sort(); - - for (let key in sorted_keys) - { - let type = sorted_keys[key]; - let p = document.createElement("p"); - p.textContent = type; - if (g_active_elements.indexOf(p.textContent) !== -1) - p.className = "active"; - choices.appendChild(p); - p.onclick = function() - { - if (g_active_elements.indexOf(p.textContent) !== -1) - { - p.className = ""; - g_active_elements = g_active_elements.filter( x => x != p.textContent); - update_analysis(); - return; - } - g_active_elements.push(p.textContent); - p.className = "active"; - update_analysis(); - } - } - update_analysis(); -} - -function update_analysis() -{ - compare_reports(); - draw_history_graph(); - draw_frequency_graph(); -} - -function load_report_from_file(evt) -{ - var file = evt.target.files[0]; - if (!file) - return; - load_report(false, file); - evt.target.value = null; -} - -function load_report(trylive, file) -{ - if (g_loading_timeout != undefined) - return; - - let reportID = g_reports.length; - let nav = document.querySelector("header nav"); - let newRep = document.createElement("p"); - newRep.textContent = file.name; - newRep.className = "loading"; - newRep.id = "Report" + reportID; - newRep.dataset.id = reportID; - nav.appendChild(newRep); - - g_reports.push(Profiler2Report(on_report_loaded, trylive, file)); - g_loading_timeout = setTimeout(function() { on_report_loaded(false); }, 5000); -} - -function on_report_loaded(success) -{ - let element = document.getElementById("Report" + (g_reports.length-1)); - let report = g_reports[g_reports.length-1]; - if (!success) - { - element.className = "fail"; - setTimeout(function() { element.parentNode.removeChild(element); clearTimeout(g_loading_timeout); g_loading_timeout = null; }, 1000 ); - g_reports = g_reports.slice(0,-1); - if (g_reports.length === 0) - g_current_report = null; - return; - } - clearTimeout(g_loading_timeout); - g_loading_timeout = null; - select_report(+element.dataset.id); - element.onclick = function() { select_report(+element.dataset.id);}; -} - -function select_report(id) -{ - if (g_current_report != undefined) - document.getElementById("Report" + g_current_report).className = ""; - document.getElementById("Report" + id).className = "active"; - g_current_report = id; - - // Load up our canvas - g_report_draw.update_display(g_reports[id],{"seconds":5}); - - recompute_choices(g_reports[id], g_main_thread); -} - -window.onload = function() -{ - // Try loading the report live - load_report(true, {"name":"live"}); - - // add new reports - document.getElementById('report_load_input').addEventListener('change', load_report_from_file, false); +// Copyright (c) 2016 Wildfire Games +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// This file is the main handler, which deals with loading reports and showing the analysis graphs +// the latter could probably be put in a separate module + +// global array of Profiler2Report objects +var g_reports = []; + +var g_main_thread = 0; +var g_current_report = 0; + +var g_profile_path = null; +var g_active_elements = []; +var g_loading_timeout = null; + +function save_as_file() +{ + $.ajax({ + url: 'http://127.0.0.1:8000/download', + success: function () { + }, + error: function (jqXHR, textStatus, errorThrown) { + } + }); +} + +function get_history_data(report, thread, type) +{ + var ret = {"time_by_frame":[], "max" : 0, "log_scale" : null}; + + var report_data = g_reports[report].data().threads[thread]; + var interval_data = report_data.intervals; + + let data = report_data.intervals_by_type_frame[type]; + if (!data) + return ret; + + let max = 0; + let avg = []; + let current_frame = 0; + for (let i = 0; i < data.length; i++) + { + ret.time_by_frame.push(0); + for (let p = 0; p < data[i].length; p++) + ret.time_by_frame[ret.time_by_frame.length-1] += interval_data[data[i][p]].duration; + } + + // somehow JS sorts 0.03 lower than 3e-7 otherwise + let sorted = ret.time_by_frame.slice(0).sort((a,b) => a-b); + ret.max = sorted[sorted.length-1]; + avg = sorted[Math.round(avg.length/2)]; + + if (ret.max > avg * 3) + ret.log_scale = true; + + return ret; +} + +function draw_frequency_graph() +{ + let canvas = document.getElementById("canvas_frequency"); + canvas._tooltips = []; + + let context = canvas.getContext("2d"); + context.clearRect(0, 0, canvas.width, canvas.height); + + let legend = document.getElementById("frequency_graph").querySelector("aside"); + legend.innerHTML = ""; + + if (!g_active_elements.length) + return; + + var series_data = {}; + var use_log_scale = null; + + var x_scale = 0; + var y_scale = 0; + var padding = 10; + + var item_nb = 0; + + var tooltip_helper = {}; + + for (let typeI in g_active_elements) + { + for (let rep in g_reports) + { + item_nb++; + let data = get_history_data(rep, g_main_thread, g_active_elements[typeI]); + let name = rep + "/" + g_active_elements[typeI]; + if (document.getElementById('fulln').checked) + series_data[name] = data.time_by_frame.sort((a,b) => a-b); + else + series_data[name] = data.time_by_frame.filter(a=>a).sort((a,b) => a-b); + if (series_data[name].length > x_scale) + x_scale = series_data[name].length; + if (data.max > y_scale) + y_scale = data.max; + if (use_log_scale === null && data.log_scale) + use_log_scale = true; + } + } + if (use_log_scale) + { + let legend_item = document.createElement("p"); + legend_item.style.borderColor = "transparent"; + legend_item.textContent = " -- log x scale -- "; + legend.appendChild(legend_item); + } + let id = 0; + for (let type in series_data) + { + let colour = graph_colour(id); + let time_by_frame = series_data[type]; + let p = 0; + let last_val = 0; + + let nb = document.createElement("p"); + nb.style.borderColor = colour; + nb.textContent = type + " - n=" + time_by_frame.length; + legend.appendChild(nb); + + for (var i = 0; i < time_by_frame.length; i++) + { + let x0 = i/time_by_frame.length*(canvas.width-padding*2) + padding; + if (i == 0) + x0 = 0; + let x1 = (i+1)/time_by_frame.length*(canvas.width-padding*2) + padding; + if (i == time_by_frame.length-1) + x1 = (time_by_frame.length-1)*canvas.width; + + let y = time_by_frame[i]/y_scale; + if (use_log_scale) + y = Math.log10(1 + time_by_frame[i]/y_scale * 9); + + context.globalCompositeOperation = "lighter"; + + context.beginPath(); + context.strokeStyle = colour + context.lineWidth = 0.5; + context.moveTo(x0,canvas.height * (1 - last_val)); + context.lineTo(x1,canvas.height * (1 - y)); + context.stroke(); + + last_val = y; + if (!tooltip_helper[Math.floor(x0)]) + tooltip_helper[Math.floor(x0)] = []; + tooltip_helper[Math.floor(x0)].push([y, type]); + } + id++; + } + + for (let i in tooltip_helper) + { + let tooltips = tooltip_helper[i]; + let text = ""; + for (let j in tooltips) + if (tooltips[j][0] != undefined && text.search(tooltips[j][1])===-1) + text += "Series " + tooltips[j][1] + ": " + time_label((tooltips[j][0])*y_scale,1) + "
"; + canvas._tooltips.push({ + 'x0': +i, 'x1': +i+1, + 'y0': 0, 'y1': canvas.height, + 'text': function(text) { return function() { return text; } }(text) + }); + } + set_tooltip_handlers(canvas); + + [0.02,0.05,0.1,0.25,0.5,0.75].forEach(function(y_val) + { + let y = y_val; + if (use_log_scale) + y = Math.log10(1 + y_val * 9); + + context.beginPath(); + context.lineWidth="1"; + context.strokeStyle = "rgba(0,0,0,0.2)"; + context.moveTo(0,canvas.height * (1- y)); + context.lineTo(canvas.width,canvas.height * (1 - y)); + context.stroke(); + context.fillStyle = "gray"; + context.font = "10px Arial"; + context.textAlign="left"; + context.fillText(time_label(y*y_scale,0), 2, canvas.height * (1 - y) - 2 ); + }); +} + +function draw_history_graph() +{ + let canvas = document.getElementById("canvas_history"); + canvas._tooltips = []; + + let context = canvas.getContext("2d"); + context.clearRect(0, 0, canvas.width, canvas.height); + + let legend = document.getElementById("history_graph").querySelector("aside"); + legend.innerHTML = ""; + + if (!g_active_elements.length) + return; + + var series_data = {}; + var use_log_scale = null; + + var frames_nb = Infinity; + var x_scale = 0; + var y_scale = 0; + + var item_nb = 0; + + var tooltip_helper = {}; + + for (let typeI in g_active_elements) + { + for (let rep in g_reports) + { + if (g_reports[rep].data().threads[g_main_thread].frames.length < frames_nb) + frames_nb = g_reports[rep].data().threads[g_main_thread].frames.length; + item_nb++; + let data = get_history_data(rep, g_main_thread, g_active_elements[typeI]); + if (!document.getElementById('smooth').value) + series_data[rep + "/" + g_active_elements[typeI]] = data.time_by_frame; + else + series_data[rep + "/" + g_active_elements[typeI]] = smooth_1D_array(data.time_by_frame, +document.getElementById('smooth').value); + if (data.max > y_scale) + y_scale = data.max; + if (use_log_scale === null && data.log_scale) + use_log_scale = true; + } + } + if (use_log_scale) + { + let legend_item = document.createElement("p"); + legend_item.style.borderColor = "transparent"; + legend_item.textContent = " -- log y scale -- "; + legend.appendChild(legend_item); + } + canvas.width = Math.max(frames_nb,600); + x_scale = frames_nb / canvas.width; + let id = 0; + for (let type in series_data) + { + let colour = graph_colour(id); + + let legend_item = document.createElement("p"); + legend_item.style.borderColor = colour; + legend_item.textContent = type; + legend.appendChild(legend_item); + + let time_by_frame = series_data[type]; + let last_val = 0; + for (var i = 0; i < frames_nb; i++) + { + let smoothed_time = time_by_frame[i];//smooth_1D(time_by_frame.slice(0), i, 3); + + let y = smoothed_time/y_scale; + if (use_log_scale) + y = Math.log10(1 + smoothed_time/y_scale * 9); + + if (item_nb === 1) + { + context.beginPath(); + context.fillStyle = colour; + context.fillRect(i/x_scale,canvas.height,1/x_scale,-y*canvas.height); + } + else + { + if ( i == frames_nb-1) + continue; + context.globalCompositeOperation = "lighten"; + context.beginPath(); + context.strokeStyle = colour + context.lineWidth = 0.5; + context.moveTo(i/x_scale,canvas.height * (1 - last_val)); + context.lineTo((i+1)/x_scale,canvas.height * (1 - y)); + context.stroke(); + } + last_val = y; + if (!tooltip_helper[Math.floor(i/x_scale)]) + tooltip_helper[Math.floor(i/x_scale)] = []; + tooltip_helper[Math.floor(i/x_scale)].push([y, type]); + } + id++; + } + + for (let i in tooltip_helper) + { + let tooltips = tooltip_helper[i]; + let text = "Frame " + i*x_scale + "
"; + for (let j in tooltips) + if (tooltips[j][0] != undefined && text.search(tooltips[j][1])===-1) + text += "Series " + tooltips[j][1] + ": " + time_label((tooltips[j][0])*y_scale,1) + "
"; + canvas._tooltips.push({ + 'x0': +i, 'x1': +i+1, + 'y0': 0, 'y1': canvas.height, + 'text': function(text) { return function() { return text; } }(text) + }); + } + set_tooltip_handlers(canvas); + + [0.1,0.25,0.5,0.75].forEach(function(y_val) + { + let y = y_val; + if (use_log_scale) + y = Math.log10(1 + y_val * 9); + + context.beginPath(); + context.lineWidth="1"; + context.strokeStyle = "rgba(0,0,0,0.2)"; + context.moveTo(0,canvas.height * (1- y)); + context.lineTo(canvas.width,canvas.height * (1 - y)); + context.stroke(); + context.fillStyle = "gray"; + context.font = "10px Arial"; + context.textAlign="left"; + context.fillText(time_label(y*y_scale,0), 2, canvas.height * (1 - y) - 2 ); + }); +} + +function compare_reports() +{ + let section = document.getElementById("comparison"); + section.innerHTML = "

Report Comparison

"; + + if (g_reports.length < 2) + { + section.innerHTML += "

Too few reports loaded

"; + return; + } + + if (g_active_elements.length != 1) + { + section.innerHTML += "

Too many of too few elements selected

"; + return; + } + + let frames_nb = g_reports[0].data().threads[g_main_thread].frames.length; + for (let rep in g_reports) + if (g_reports[rep].data().threads[g_main_thread].frames.length < frames_nb) + frames_nb = g_reports[rep].data().threads[g_main_thread].frames.length; + + if (frames_nb != g_reports[0].data().threads[g_main_thread].frames.length) + section.innerHTML += "

Only the first " + frames_nb + " frames will be considered.

"; + + let reports_data = []; + + for (let rep in g_reports) + { + let raw_data = get_history_data(rep, g_main_thread, g_active_elements[0]).time_by_frame; + reports_data.push({"time_data" : raw_data.slice(0,frames_nb), "sorted_data" : raw_data.slice(0,frames_nb).sort((a,b) => a-b)}); + } + + let table_output = "" + for (let rep in reports_data) + { + let report = reports_data[rep]; + table_output += ""; + // median + table_output += "" + // max + table_output += "" + let frames_better = 0; + let frames_diff = 0; + for (let f in report.time_data) + { + if (report.time_data[f] <= reports_data[0].time_data[f]) + frames_better++; + frames_diff += report.time_data[f] - reports_data[0].time_data[f]; + } + table_output += ""; + table_output += ""; + } + section.innerHTML += table_output + "
Profiler VariableMedianMaximum% better framestime difference per frame
Report " + rep + (rep == 0 ? " (reference)":"") + "" + time_label(report.sorted_data[Math.floor(report.sorted_data.length/2)]) + "" + time_label(report.sorted_data[report.sorted_data.length-1]) + "" + (frames_better/frames_nb*100).toFixed(0) + "%" + time_label((frames_diff/frames_nb),2) + "
"; +} + +function recompute_choices(report, thread) +{ + var choices = document.getElementById("choices").querySelector("nav"); + choices.innerHTML = "

Choices

"; + var types = {}; + var data = report.data().threads[thread]; + + for (let i = 0; i < data.intervals.length; i++) + types[data.intervals[i].id] = 0; + + var sorted_keys = Object.keys(types).sort(); + + for (let key in sorted_keys) + { + let type = sorted_keys[key]; + let p = document.createElement("p"); + p.textContent = type; + if (g_active_elements.indexOf(p.textContent) !== -1) + p.className = "active"; + choices.appendChild(p); + p.onclick = function() + { + if (g_active_elements.indexOf(p.textContent) !== -1) + { + p.className = ""; + g_active_elements = g_active_elements.filter( x => x != p.textContent); + update_analysis(); + return; + } + g_active_elements.push(p.textContent); + p.className = "active"; + update_analysis(); + } + } + update_analysis(); +} + +function update_analysis() +{ + compare_reports(); + draw_history_graph(); + draw_frequency_graph(); +} + +function load_report_from_file(evt) +{ + var file = evt.target.files[0]; + if (!file) + return; + load_report(false, file); + evt.target.value = null; +} + +function load_report(trylive, file) +{ + if (g_loading_timeout != undefined) + return; + + let reportID = g_reports.length; + let nav = document.querySelector("header nav"); + let newRep = document.createElement("p"); + newRep.textContent = file.name; + newRep.className = "loading"; + newRep.id = "Report" + reportID; + newRep.dataset.id = reportID; + nav.appendChild(newRep); + + g_reports.push(Profiler2Report(on_report_loaded, trylive, file)); + g_loading_timeout = setTimeout(function() { on_report_loaded(false); }, 5000); +} + +function on_report_loaded(success) +{ + let element = document.getElementById("Report" + (g_reports.length-1)); + let report = g_reports[g_reports.length-1]; + if (!success) + { + element.className = "fail"; + setTimeout(function() { element.parentNode.removeChild(element); clearTimeout(g_loading_timeout); g_loading_timeout = null; }, 1000 ); + g_reports = g_reports.slice(0,-1); + if (g_reports.length === 0) + g_current_report = null; + return; + } + clearTimeout(g_loading_timeout); + g_loading_timeout = null; + select_report(+element.dataset.id); + element.onclick = function() { select_report(+element.dataset.id);}; +} + +function select_report(id) +{ + if (g_current_report != undefined) + document.getElementById("Report" + g_current_report).className = ""; + document.getElementById("Report" + id).className = "active"; + g_current_report = id; + + // Load up our canvas + g_report_draw.update_display(g_reports[id],{"seconds":5}); + + recompute_choices(g_reports[id], g_main_thread); +} + +window.onload = function() +{ + // Try loading the report live + load_report(true, {"name":"live"}); + + // add new reports + document.getElementById('report_load_input').addEventListener('change', load_report_from_file, false); } \ No newline at end of file diff --git a/source/tools/profiler2/utilities.js b/source/tools/profiler2/utilities.js index caf6dc4828..12fb0185eb 100644 --- a/source/tools/profiler2/utilities.js +++ b/source/tools/profiler2/utilities.js @@ -1,192 +1,192 @@ -// Copyright (c) 2016 Wildfire Games -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -// Various functions used by several of the tiles. - -function hslToRgb(h, s, l, a) -{ - var r, g, b; - - if (s == 0) - { - r = g = b = l; - } - else - { - function hue2rgb(p, q, t) - { - if (t < 0) t += 1; - if (t > 1) t -= 1; - if (t < 1/6) return p + (q - p) * 6 * t; - if (t < 1/2) return q; - if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; - return p; - } - - var q = l < 0.5 ? l * (1 + s) : l + s - l * s; - var p = 2 * l - q; - r = hue2rgb(p, q, h + 1/3); - g = hue2rgb(p, q, h); - b = hue2rgb(p, q, h - 1/3); - } - - return 'rgba(' + Math.floor(r * 255) + ',' + Math.floor(g * 255) + ',' + Math.floor(b * 255) + ',' + a + ')'; -} - -function new_colour(id) -{ - var hs = [0, 1/3, 2/3, 1/4, 2/4, 3/4, 1/5, 3/5, 2/5, 4/5]; - var ss = [1, 0.5]; - var ls = [0.8, 0.6, 0.9, 0.7]; - return hslToRgb(hs[id % hs.length], ss[Math.floor(id / hs.length) % ss.length], ls[Math.floor(id / (hs.length*ss.length)) % ls.length], 1); -} - -function graph_colour(id) -{ - var hs = [0, 1/3, 2/3, 2/4, 3/4, 1/5, 3/5, 2/5, 4/5]; - return hslToRgb(hs[id % hs.length], 0.7, 0.5, 1); -} - -function concat_events(data) -{ - var events = []; - data.events.forEach(function(ev) { - ev.pop(); // remove the dummy null markers - Array.prototype.push.apply(events, ev); - }); - return events; -} - -function time_label(t, precision = 2) -{ - if (t < 0) - return "-" + time_label(-t, precision); - if (t > 1e-3) - return (t * 1e3).toFixed(precision) + 'ms'; - else - return (t * 1e6).toFixed(precision) + 'us'; -} - -function slice_intervals(data, range) -{ - if (!data.intervals.length) - return {"tmin":0,"tmax":0,"intervals":[]}; - - var tmin = 0; - var tmax = 0; - if (range.seconds && data.frames.length) - { - tmax = data.frames[data.frames.length-1].t1; - tmin = data.frames[data.frames.length-1].t1-range.seconds; - } - else if (range.frames && data.frames.length) - { - tmax = data.frames[data.frames.length-1].t1; - tmin = data.frames[data.frames.length-1-range.frames].t0; - } - else - { - tmax = range.tmax; - tmin = range.tmin; - } - var events = { "tmin" : tmin, "tmax" : tmax, "intervals" : [] }; - for (let itv in data.intervals) - { - let interval = data.intervals[itv]; - if (interval.t1 > tmin && interval.t0 < tmax) - events.intervals.push(interval); - } - return events; -} - -function smooth_1D(array, i, distance) -{ - let value = 0; - let total = 0; - for (let j = i - distance; j <= i + distance; j++) - { - value += array[j]*(1+distance*distance - (j-i)*(j-i) ); - total += (1+distance*distance - (j-i)*(j-i) ); - } - return value/total; -} - -function smooth_1D_array(array, distance) -{ - let copied = array.slice(0); - for (let i =0; i < array.length; ++i) - { - let value = 0; - let total = 0; - for (let j = i - distance; j <= i + distance; j++) - { - value += array[j]*(1+distance*distance - (j-i)*(j-i) ); - total += (1+distance*distance - (j-i)*(j-i) ); - } - copied[i] = value/total; - } - return copied; -} - -function set_tooltip_handlers(canvas) -{ - function do_tooltip(event) - { - var tooltips = canvas._tooltips; - if (!tooltips) - return; - - var relativeX = event.pageX - this.getBoundingClientRect().left - window.scrollX; - var relativeY = event.pageY - this.getBoundingClientRect().top - window.scrollY; - - var text = undefined; - for (var i = 0; i < tooltips.length; ++i) - { - var t = tooltips[i]; - if (t.x0-1 <= relativeX && relativeX <= t.x1+1 && t.y0 <= relativeY && relativeY <= t.y1) - { - text = t.text(); - break; - } - } - if (text) - { - if (text.length > 512) - $('#tooltip').addClass('long'); - else - $('#tooltip').removeClass('long'); - $('#tooltip').css('left', (event.pageX+16)+'px'); - $('#tooltip').css('top', (event.pageY+8)+'px'); - $('#tooltip').html(text); - $('#tooltip').css('visibility', 'visible'); - } - else - { - $('#tooltip').css('visibility', 'hidden'); - } - } - - $(canvas).mousemove(function(event) { - do_tooltip.call(this, event); - }); - $(canvas).mouseleave(function(event) { - $('#tooltip').css('visibility', 'hidden'); - }); +// Copyright (c) 2016 Wildfire Games +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Various functions used by several of the tiles. + +function hslToRgb(h, s, l, a) +{ + var r, g, b; + + if (s == 0) + { + r = g = b = l; + } + else + { + function hue2rgb(p, q, t) + { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1/6) return p + (q - p) * 6 * t; + if (t < 1/2) return q; + if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; + return p; + } + + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + r = hue2rgb(p, q, h + 1/3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1/3); + } + + return 'rgba(' + Math.floor(r * 255) + ',' + Math.floor(g * 255) + ',' + Math.floor(b * 255) + ',' + a + ')'; +} + +function new_colour(id) +{ + var hs = [0, 1/3, 2/3, 1/4, 2/4, 3/4, 1/5, 3/5, 2/5, 4/5]; + var ss = [1, 0.5]; + var ls = [0.8, 0.6, 0.9, 0.7]; + return hslToRgb(hs[id % hs.length], ss[Math.floor(id / hs.length) % ss.length], ls[Math.floor(id / (hs.length*ss.length)) % ls.length], 1); +} + +function graph_colour(id) +{ + var hs = [0, 1/3, 2/3, 2/4, 3/4, 1/5, 3/5, 2/5, 4/5]; + return hslToRgb(hs[id % hs.length], 0.7, 0.5, 1); +} + +function concat_events(data) +{ + var events = []; + data.events.forEach(function(ev) { + ev.pop(); // remove the dummy null markers + Array.prototype.push.apply(events, ev); + }); + return events; +} + +function time_label(t, precision = 2) +{ + if (t < 0) + return "-" + time_label(-t, precision); + if (t > 1e-3) + return (t * 1e3).toFixed(precision) + 'ms'; + else + return (t * 1e6).toFixed(precision) + 'us'; +} + +function slice_intervals(data, range) +{ + if (!data.intervals.length) + return {"tmin":0,"tmax":0,"intervals":[]}; + + var tmin = 0; + var tmax = 0; + if (range.seconds && data.frames.length) + { + tmax = data.frames[data.frames.length-1].t1; + tmin = data.frames[data.frames.length-1].t1-range.seconds; + } + else if (range.frames && data.frames.length) + { + tmax = data.frames[data.frames.length-1].t1; + tmin = data.frames[data.frames.length-1-range.frames].t0; + } + else + { + tmax = range.tmax; + tmin = range.tmin; + } + var events = { "tmin" : tmin, "tmax" : tmax, "intervals" : [] }; + for (let itv in data.intervals) + { + let interval = data.intervals[itv]; + if (interval.t1 > tmin && interval.t0 < tmax) + events.intervals.push(interval); + } + return events; +} + +function smooth_1D(array, i, distance) +{ + let value = 0; + let total = 0; + for (let j = i - distance; j <= i + distance; j++) + { + value += array[j]*(1+distance*distance - (j-i)*(j-i) ); + total += (1+distance*distance - (j-i)*(j-i) ); + } + return value/total; +} + +function smooth_1D_array(array, distance) +{ + let copied = array.slice(0); + for (let i =0; i < array.length; ++i) + { + let value = 0; + let total = 0; + for (let j = i - distance; j <= i + distance; j++) + { + value += array[j]*(1+distance*distance - (j-i)*(j-i) ); + total += (1+distance*distance - (j-i)*(j-i) ); + } + copied[i] = value/total; + } + return copied; +} + +function set_tooltip_handlers(canvas) +{ + function do_tooltip(event) + { + var tooltips = canvas._tooltips; + if (!tooltips) + return; + + var relativeX = event.pageX - this.getBoundingClientRect().left - window.scrollX; + var relativeY = event.pageY - this.getBoundingClientRect().top - window.scrollY; + + var text = undefined; + for (var i = 0; i < tooltips.length; ++i) + { + var t = tooltips[i]; + if (t.x0-1 <= relativeX && relativeX <= t.x1+1 && t.y0 <= relativeY && relativeY <= t.y1) + { + text = t.text(); + break; + } + } + if (text) + { + if (text.length > 512) + $('#tooltip').addClass('long'); + else + $('#tooltip').removeClass('long'); + $('#tooltip').css('left', (event.pageX+16)+'px'); + $('#tooltip').css('top', (event.pageY+8)+'px'); + $('#tooltip').html(text); + $('#tooltip').css('visibility', 'visible'); + } + else + { + $('#tooltip').css('visibility', 'hidden'); + } + } + + $(canvas).mousemove(function(event) { + do_tooltip.call(this, event); + }); + $(canvas).mouseleave(function(event) { + $('#tooltip').css('visibility', 'hidden'); + }); } \ No newline at end of file diff --git a/source/tools/webservices/userreport/templatetags/cycle.py b/source/tools/webservices/userreport/templatetags/cycle.py index 5f36bb9aa4..6145a8eb9f 100644 --- a/source/tools/webservices/userreport/templatetags/cycle.py +++ b/source/tools/webservices/userreport/templatetags/cycle.py @@ -1,99 +1,99 @@ -# http://code.djangoproject.com/attachment/ticket/5908/cycle.py - -from django.utils.translation import ungettext, ugettext as _ -from django.utils.encoding import force_unicode -from django import template -from django.template import defaultfilters -from django.template import Node, Variable -from django.conf import settings -from itertools import cycle as itertools_cycle - -register = template.Library() - - - -class SafeCycleNode(Node): - def __init__(self, cyclevars, variable_name=None): - self.cyclevars = cyclevars - self.cycle_iter = itertools_cycle(cyclevars) - self.variable_name = variable_name - - def render(self, context): - if context.has_key('forloop'): - if not context.get(self): - context[self] = True - self.cycle_iter = itertools_cycle(self.cyclevars) - value = self.cycle_iter.next() - value = Variable(value).resolve(context) - if self.variable_name: - context[self.variable_name] = value - return value - - - -#@register.tag -def safe_cycle(parser, token): - """ - Cycles among the given strings each time this tag is encountered. - - Within a loop, cycles among the given strings each time through - the loop:: - - {% for o in some_list %} - - ... - - {% endfor %} - - Outside of a loop, give the values a unique name the first time you call - it, then use that name each sucessive time through:: - - ... - ... - ... - - You can use any number of values, seperated by spaces. Commas can also - be used to separate values; if a comma is used, the cycle values are - interpreted as literal strings. - """ - - # Note: This returns the exact same node on each {% cycle name %} call; - # that is, the node object returned from {% cycle a b c as name %} and the - # one returned from {% cycle name %} are the exact same object. This - # shouldn't cause problems (heh), but if it does, now you know. - # - # Ugly hack warning: this stuffs the named template dict into parser so - # that names are only unique within each template (as opposed to using - # a global variable, which would make cycle names have to be unique across - # *all* templates. - - args = token.split_contents() - - if len(args) < 2: - raise TemplateSyntaxError("'cycle' tag requires at least two arguments") - - if ',' in args[1]: - # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %} - # case. - args[1:2] = ['"%s"' % arg for arg in args[1].split(",")] - - if len(args) == 2: - # {% cycle foo %} case. - name = args[1] - if not hasattr(parser, '_namedCycleNodes'): - raise TemplateSyntaxError("No named cycles in template." - " '%s' is not defined" % name) - if not name in parser._namedCycleNodes: - raise TemplateSyntaxError("Named cycle '%s' does not exist" % name) - return parser._namedCycleNodes[name] - - if len(args) > 4 and args[-2] == 'as': - name = args[-1] - node = SafeCycleNode(args[1:-2], name) - if not hasattr(parser, '_namedCycleNodes'): - parser._namedCycleNodes = {} - parser._namedCycleNodes[name] = node - else: - node = SafeCycleNode(args[1:]) - return node -safe_cycle = register.tag(safe_cycle) +# http://code.djangoproject.com/attachment/ticket/5908/cycle.py + +from django.utils.translation import ungettext, ugettext as _ +from django.utils.encoding import force_unicode +from django import template +from django.template import defaultfilters +from django.template import Node, Variable +from django.conf import settings +from itertools import cycle as itertools_cycle + +register = template.Library() + + + +class SafeCycleNode(Node): + def __init__(self, cyclevars, variable_name=None): + self.cyclevars = cyclevars + self.cycle_iter = itertools_cycle(cyclevars) + self.variable_name = variable_name + + def render(self, context): + if context.has_key('forloop'): + if not context.get(self): + context[self] = True + self.cycle_iter = itertools_cycle(self.cyclevars) + value = self.cycle_iter.next() + value = Variable(value).resolve(context) + if self.variable_name: + context[self.variable_name] = value + return value + + + +#@register.tag +def safe_cycle(parser, token): + """ + Cycles among the given strings each time this tag is encountered. + + Within a loop, cycles among the given strings each time through + the loop:: + + {% for o in some_list %} + + ... + + {% endfor %} + + Outside of a loop, give the values a unique name the first time you call + it, then use that name each sucessive time through:: + + ... + ... + ... + + You can use any number of values, seperated by spaces. Commas can also + be used to separate values; if a comma is used, the cycle values are + interpreted as literal strings. + """ + + # Note: This returns the exact same node on each {% cycle name %} call; + # that is, the node object returned from {% cycle a b c as name %} and the + # one returned from {% cycle name %} are the exact same object. This + # shouldn't cause problems (heh), but if it does, now you know. + # + # Ugly hack warning: this stuffs the named template dict into parser so + # that names are only unique within each template (as opposed to using + # a global variable, which would make cycle names have to be unique across + # *all* templates. + + args = token.split_contents() + + if len(args) < 2: + raise TemplateSyntaxError("'cycle' tag requires at least two arguments") + + if ',' in args[1]: + # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %} + # case. + args[1:2] = ['"%s"' % arg for arg in args[1].split(",")] + + if len(args) == 2: + # {% cycle foo %} case. + name = args[1] + if not hasattr(parser, '_namedCycleNodes'): + raise TemplateSyntaxError("No named cycles in template." + " '%s' is not defined" % name) + if not name in parser._namedCycleNodes: + raise TemplateSyntaxError("Named cycle '%s' does not exist" % name) + return parser._namedCycleNodes[name] + + if len(args) > 4 and args[-2] == 'as': + name = args[-1] + node = SafeCycleNode(args[1:-2], name) + if not hasattr(parser, '_namedCycleNodes'): + parser._namedCycleNodes = {} + parser._namedCycleNodes[name] = node + else: + node = SafeCycleNode(args[1:]) + return node +safe_cycle = register.tag(safe_cycle)