diff --git a/include/lgi/common/LgiCommon.h b/include/lgi/common/LgiCommon.h --- a/include/lgi/common/LgiCommon.h +++ b/include/lgi/common/LgiCommon.h @@ -1,417 +1,417 @@ /** \file \author Matthew Allen \date 27/3/2000 \brief Common LGI include Copyright (C) 2000-2004, Matthew Allen */ /** * \defgroup Base Foundation tools * \ingroup Lgi */ /** * \defgroup Text Text handling * \ingroup Lgi */ #ifndef _LGI_COMMON_H #define _LGI_COMMON_H #if defined LINUX #include #endif #include "lgi/common/Mem.h" #include "lgi/common/Array.h" #include "lgi/common/LgiString.h" #include "lgi/common/StringClass.h" #include "lgi/common/CurrentTime.h" #include "lgi/common/LgiUiBase.h" /// Returns the system path specified /// \ingroup Base LgiExtern LString LGetSystemPath( /// Which path to retreive LSystemPath Which, int WordSize = 0 ); /// Gets the path of the currently running executable /// \ingroup Base LgiExtern LString LGetExePath(); /// Gets the file of the currently running executable /// \ingroup Base LgiExtern LString LGetExeFile(); /// Returns the mime type of the file /// \ingroup Mime LgiExtern LString LGetFileMimeType ( /// File to find mime type for const char *File ); /// Finds a file in the applications directory or nearby /// \ingroup Base LgiExtern LString LFindFile(const char *Name); /// Returns the application associated with the mime type /// \ingroup Mime LgiExtern LString LGetAppForMimeType ( /// Type of the file to find and app for const char *Mime ); /// \return a formatted file size LgiExtern LString LFormatSize(int64_t Size); /// URL encode a string LgiExtern LString LUrlEncode(const char *s, const char *delim); /// URL decode a string LgiExtern LString LUrlDecode(const char *s); /// Gets the current user LgiExtern LString LCurrentUserName(); /// Returns an environment variable. LgiExtern LString LGetEnv(const char *Var); /// Gets the system path.. LgiExtern LString::Array LGetPath(); /// Check for a valid email string LgiExtern bool LIsValidEmail(LString Email); /// Finds an application to handle a protocol request (e.g. 'mailto', 'http' etc) LgiExtern LString LGetAppForProtocol(const char *Protocol); /// Converts a string to the native 8bit charset of the OS from utf-8 /// \ingroup Text LgiExtern LString LToNativeCp(const char *In, ssize_t InLen = -1); /// Converts a string from the native 8bit charset of the OS to utf-8 /// \ingroup Text LgiExtern LString LFromNativeCp(const char *In, ssize_t InLen = -1); LgiExtern LString LStrConvertCp ( /// Output charset const char *OutCp, /// Input buffer const void *In, /// The input data's charset const char *InCp, /// Bytes of valid data in the input ssize_t InLen = -1 ); #ifdef __cplusplus extern "C" { #endif ///////////////////////////////////////////////////////////// // Externs // Codepages /// Converts a buffer of text to a different charset /// \ingroup Text /// \returns the bytes written to the location pointed to by 'Out' LgiFunc ssize_t LBufConvertCp(void *Out, const char *OutCp, ssize_t OutLen, const void *&In, const char *InCp, ssize_t &InLen); /// \brief Converts a string to a new charset /// \return A dynamically allocate, null terminated string in the new charset /// \ingroup Text LgiFunc void *LNewConvertCp ( /// Output charset - const char *OutCp, + const char *OutCharset, /// Input buffer const void *In, /// The input data's charset - const char *InCp, + const char *InCharset, /// Bytes of valid data in the input ssize_t InLen = -1 ); /// Return true if Lgi support the charset /// \ingroup Text LgiFunc bool LIsCpImplemented(const char *Cp); /// Converts the ANSI code page to a charset name /// \ingroup Text LgiFunc const char *LAnsiToLgiCp(int AnsiCodePage = -1); /// Calculate the number of characters in a string /// \ingroup Text LgiFunc int LCharLen(const void *Str, const char *Cp, int Bytes = -1); /// Move a pointer along a utf-8 string by characters /// \ingroup Text LgiFunc char *LSeekUtf8 ( /// Pointer to the current character const char *Ptr, /// The number of characters to move forward or back ssize_t D, /// The start of the memory buffer if you known char *Start = 0 ); /// Return true if the string is valid utf-8 /// \ingroup Text LgiFunc bool LIsUtf8(const char *s, ssize_t len = -1); /// Returns the next token in a string, leaving the argument pointing to the end of the token /// \ingroup Text LgiFunc char *LTokStr(const char *&s); /// Formats a data size into appropriate units /// \ingroup Base LgiFunc void LFormatSize ( /// Output string char *Str, /// Output string buffer length int SLen, /// Input size in bytes int64_t Size ); /// \returns true if the path is a volume root. LgiFunc bool LIsVolumeRoot(const char *Path); /// Converts a string from URI encoding (ala %20 -> ' ') /// \returns a dynamically allocated string or NULL on error /// \ingroup Text LgiFunc char *LDecodeUri ( /// The URI const char *uri, /// The length or -1 if NULL terminated int len = -1 ); /// Converts a string to URI encoding (ala %20 -> ' ') /// \returns a dynamically allocated string or NULL on error /// \ingroup Text LgiFunc char *LEncodeUri ( /// The URI const char *uri, /// The length or -1 if NULL terminated int len = -1 ); // Path #if LGI_COCOA || defined(__GTK_H__) || defined(HAIKU) LgiExtern LString LgiArgsAppPath; #endif /// Returns the system path specified /// \ingroup Base LgiFunc bool LGetSystemPath ( /// Which path to retreive LSystemPath Which, /// The buffer to receive the path into char *Dst, /// The size of the receive buffer in bytes ssize_t DstSize ); /// \brief Recursively search for files /// \return Non zero if something was found /// \ingroup Base LgiFunc bool LRecursiveFileSearch ( /// Start search in this dir const char *Root, /// Extensions to match LArray *Ext = NULL, /// [optional] Output filenames LArray *Files = NULL, /// [optional] Output total size uint64 *Size = NULL, /// [optional] File count uint64 *Count = NULL, /// [optional] Callback for match std::function Callback = NULL, /// [optional] Cancel object LCancel *Cancel = NULL ); // Resources /// Gets the currently selected language /// \ingroup Resources LgiFunc struct LLanguage *LGetLanguageId(); // Os version functions /// Gets the current operating system and optionally it's version. /// \returns One of the defines starting with #LGI_OS_UNKNOWN in LgiDefs.h /// \ingroup Base LgiFunc int LGetOs(LArray *Ver = 0); /// Gets the current operation systems name. /// \ingroup Base LgiFunc const char *LGetOsName(); // System /// \brief Opens a file or directory. /// /// If the input is an executable then it is run. If the input file /// is a document then an appropriate application is found to open the /// file and the file is passed to that application. If the input is /// a directory then the OS's file manager is openned to browse the /// directory. /// /// \ingroup Base LgiFunc bool LExecute ( /// The file to open const char *File, /// The arguments to pass to the program const char *Arguments="", /// The directory to run in const char *Dir = 0, /// An error message LString *ErrorMsg = NULL ); /// Initializes the random number generator /// \ingroup Base LgiFunc void LRandomize(uint Seed); /// Returns a random number between 0 and Max-1 /// \ingroup Base LgiFunc uint LRand(uint Max = 0); LgiFunc bool _lgi_read_colour_config(const char *Tag, uint32_t *c); #ifndef SND_ASYNC #define SND_ASYNC 0x0001 #endif /// Plays a sound /// \ingroup Base LgiFunc bool LPlaySound ( /// File name of the sound to play const char *FileName, /// 0 or SND_ASYNC. If 0 the function blocks till the sound finishes. int Flags ); /** * \defgroup Mime Mime handling support. * \ingroup Lgi */ /// Returns the file extensions associated with the mimetype /// \ingroup Mime LgiExtern bool LGetMimeTypeExtensions ( /// The returned mime type const char *Mime, /// The extensions LArray &Ext ); inline bool LGetAppForMimeType(const char *Mime, char *AppPath, int BufSize) { LString p = LGetAppForMimeType(Mime); if (AppPath && p) strcpy_s(AppPath, BufSize, p); return p.Length() > 0; } /// Returns the all applications that can open a given mime type. /// \ingroup Mime LgiFunc bool LGetAppsForMimeType ( /// The type of files to match apps to. /// /// Two special cases exist: /// - application/email gets the default email client /// - application/browser get the default web browser const char *Mime, /// The applications that can handle the LArray &Apps, /// Limit the length of the results, i.e. stop looking after 'Limit' matches. /// -1 means return all matches. int Limit = -1 ); // Debug /// Returns true if the build is for release. /// \ingroup Base LgiFunc int LIsReleaseBuild(); #if defined WIN32 /// Registers an active x control LgiFunc bool RegisterActiveXControl(const char *Dll); enum HWBRK_TYPE { HWBRK_TYPE_CODE, HWBRK_TYPE_READWRITE, HWBRK_TYPE_WRITE, }; enum HWBRK_SIZE { HWBRK_SIZE_1, HWBRK_SIZE_2, HWBRK_SIZE_4, HWBRK_SIZE_8, }; /// Set a hardware breakpoint. LgiFunc HANDLE SetHardwareBreakpoint ( /// Use GetCurrentThread() HANDLE hThread, /// Type of breakpoint HWBRK_TYPE Type, /// Size of breakpoint HWBRK_SIZE Size, /// The pointer to the data to break on void *s ); /// Deletes a hardware breakpoint LgiFunc bool RemoveHardwareBreakpoint(HANDLE hBrk); #elif defined LINUX /// Window managers enum WindowManager { WM_Unknown, WM_Kde, WM_Gnome }; /// Returns the currently running window manager WindowManager LGetWindowManager(); #elif defined(__OBJC__) LgiFunc NSCursor *LCocoaCursor(LCursor lc); #endif #ifdef __cplusplus } #endif #endif diff --git a/include/lgi/common/TextConvert.h b/include/lgi/common/TextConvert.h --- a/include/lgi/common/TextConvert.h +++ b/include/lgi/common/TextConvert.h @@ -1,15 +1,15 @@ #pragma once bool Is8Bit(const char *Text); [[deprecated]] char *DecodeBase64Str(char *Str, ssize_t Len = -1); LString LDecodeBase64Str(LString Str); [[deprecated]] char *DecodeQuotedPrintableStr(char *Str, ssize_t Len = -1); LString LDecodeQuotedPrintableStr(LString Str); [[deprecated]] char *DecodeRfc2047(char *Str); LString LDecodeRfc2047(LString Str); -[[deprecated]] char *EncodeRfc2047(char *Str, const char *CodePage, List *CharsetPrefs, ssize_t LineLength = 0); -LString LEncodeRfc2047(LString Str, const char *CodePage, List *CharsetPrefs, ssize_t LineLength = 0); +[[deprecated]] char *EncodeRfc2047(char *Str, const char *Charset, List *CharsetPrefs, ssize_t LineLength = 0); +LString LEncodeRfc2047(LString Str, const char *Charset, List *CharsetPrefs, ssize_t LineLength = 0); diff --git a/src/common/Gdc2/Font/Charset.cpp b/src/common/Gdc2/Font/Charset.cpp --- a/src/common/Gdc2/Font/Charset.cpp +++ b/src/common/Gdc2/Font/Charset.cpp @@ -1,1711 +1,1711 @@ #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Font.h" #include "lgi/common/Charset.h" struct UnicodeMappings { int Unicode; char Ascii; } MissingMaps[] = { {0x2019, '\''}, {0x201C, '\"'}, {0x201D, '\"'}, {0, 0} }; typedef uint32_t iso2022jp_block[16]; iso2022jp_block *iso2022jp_map[128]; iso2022jp_block iso2022jp_blocks[] = { {0,0,0x10000000,0,0,0x53118c,0x800000,0x800000,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0xfffe0000,0xfffe03fb,0x3fb,0}, {0xffff0002,0xffffffff,0x2ffff,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0x33510000,0x80d0063,0,0,0,0,0,0,0x8,0x800,0,0,0xf0000,0,0x140000,0}, {0x6404098d,0x20301f81,0x40000,0xcc3,0xcc,0x20,0,0,0x40000,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0x3999900f,0x99999939,0x804,0,0,0x300c0003,0xc8c0,0x8000}, {0x60,0,0x5,0xa400,0,0,0,0,0,0,0,0,0,0,0,0}, {0x103fffef,0,0xfffffffe,0xffffffff,0x780fffff,0xfffffffe,0xffffffff,0x787fffff,0,0,0,0,0,0,0,0}, {0x43f36f8b,0x9b462442,0xe3e0e82c,0x400a0004,0xdb365f65,0x4497977,0xe3f0ecd7,0x8c56038,0x3403e602,0x35518000,0x7eabe0c8,0x98698200,0x2942a948,0x8060e803,0xad93441c,0x4568c03a}, {0x8656aa60,0x2403f7a,0x14618388,0x21741020,0x7022021,0x40bc3000,0x4462a624,0xa2060a8,0x85740217,0x9c840402,0x14157bfb,0x11e27f24,0x2efb665,0x20ff1f75,0x38403a70,0x676326c3}, {0x20924dd9,0xfc946b0,0x4850bc98,0xa03f8638,0x88162388,0x52323e09,0xe3a422aa,0xc72c00dd,0x26e1a166,0x8f0a840b,0x559e27eb,0x89bbc241,0x85400014,0x8496361,0x8ad07f0c,0x5cfff3e}, {0xa803ff1a,0x7b407a41,0x80024745,0x38eb0500,0x5d851,0x710c9934,0x1000397,0x24046366,0x5180d0,0x430ac000,0x30c89071,0x58000008,0xf7000e99,0x415f80,0x941000b0,0x62800018}, {0x9d00240,0x1568200,0x8015004,0x5101d10,0x1084c1,0x10504025,0x4d8a410f,0xa60d4009,0x914cab19,0x98121c0,0x3c485,0x80000652,0x80b04,0x9041d,0x905c4849,0x16900009}, {0x22200c65,0x24338412,0x47960c03,0x42250a04,0x90880028,0x4f084900,0xd3aa14a2,0x3e87d830,0x1f618604,0x41867ea4,0x5b3c390,0x211857a5,0x2a48241e,0x4a041128,0x161b0a40,0x88400d60}, {0x9502020a,0x10608221,0x4000243,0x80001444,0xc040000,0x70000000,0xc11a06,0xc00024a,0x401a00,0x40451404,0xbdb30029,0x52b0a78,0xbfa0bba9,0x8379407c,0xe81d12fc,0xc5694bf6}, {0x44aeff6,0xff022115,0x402bed63,0x242d033,0x131000,0x59ca1b02,0x20000a0,0x2c41a703,0x8ff24880,0x204,0x10055800,0x489200,0x20011894,0x34805004,0x684c3200,0x68be49ea}, {0x2e42184c,0x21c9a820,0x80b050b9,0xff7c001e,0x14e0849a,0x1e028c1,0xac49870e,0xdddb130f,0x89fbbe1a,0x51a2a2e0,0x32ca5502,0x928b3e46,0x438f1dbf,0x32186703,0x33c03028,0xa9230811}, {0x3a65c000,0x4028fe3,0x86252c4e,0xa1bf3d,0x8cd43a1a,0x317c06c9,0x950a00e0,0xedb018b,0x8c20e34b,0xf0101182,0xa7287d94,0x40fbc9ac,0x6534484,0x44445a90,0x13fc8,0xf5d40048}, {0xec577701,0x891dc442,0x49286b83,0xd2424109,0x59fe061d,0x3a221800,0x3b9fb7e4,0xc0eaf003,0x82021386,0xe4008980,0x10a1b200,0xcc44b80,0x8944d309,0x48341faf,0xc458259,0x450420a}, {0x10c8a040,0x44503140,0x1004004,0x5408280,0x442c0108,0x1a056a30,0x51420a6,0x645690cf,0x31000021,0xcbf09c18,0x63e2a120,0x1b5104c,0x9a83538c,0x3281b8b2,0xa84987a,0xc0233e7}, {0x9018d4cc,0x9070a1a1,0xe0048a1e,0x451c3d4,0x21c2439a,0x53104844,0x36400292,0xf3bd0241,0xe8f0ab09,0xa5d27dc0,0xd24bc242,0xd0afa43f,0x34a11aa0,0x3d88247,0x651bc452,0xc83ad294}, {0x40c8001c,0x33140e06,0xb21b614f,0xc0d00088,0xa898a02a,0x166ba1c5,0x85b42e50,0x604c08b,0x1e04f933,0xa251056e,0x76380400,0x73b8ec07,0x18324406,0xc8164081,0x63097c8a,0xaa042980}, {0xca9c1c24,0x27604e0e,0x83000990,0x81040046,0x10816011,0x908540d,0xcc0a000e,0xc000500,0xa0440430,0x6784008b,0x8a195288,0x8b18865e,0x41602e59,0x9cbe8c10,0x891c6861,0x89800}, {0x89a8100,0x41900018,0xe4a14007,0x640d0505,0xe4d310e,0xff0a4806,0x2aa81632,0xb852e,0xca841800,0x696c0e20,0x16000032,0x3905658,0x1a285120,0x11248000,0x432618e1,0xeaa5d52}, {0xae280fa0,0x4500fa7b,0x89406408,0xc044c880,0xb1419005,0x24c48424,0x603a1a34,0xc1949000,0x3a8246,0xc106180d,0x99100022,0x1511e050,0x824057,0x20a041a,0x8930004f,0x444ad813}, {0xed228a02,0x400510c0,0x1021000,0x31018808,0x2044600,0x708f000,0xa2008900,0x22020000,0x16100200,0x10400042,0x2605200,0x200052f4,0x82308510,0x42021100,0x80b54308,0x9a2070e1}, {0x8012040,0xfc653500,0xab0419c1,0x62140286,0x440087,0x2449085,0xa85405c,0x33803207,0xb8c00400,0xc0d0ce20,0x80c030,0xd250508,0x400a90,0x80c0200,0x40006505,0x41026421}, {0x268,0x847c0024,0xde200002,0x40498619,0x40000808,0x20010084,0x10108400,0x1c742cd,0xd52a7038,0x1d8f1968,0x3e12be50,0x81d92ef5,0x2412cec4,0x732e0828,0x4b3424ac,0xd41d020c}, {0x80002a02,0x8110097,0x114411c4,0x7d451786,0x64949d9,0x87914000,0xd8c4254c,0x491444ba,0xc8001b92,0x15800271,0xc000081,0xc200096a,0x40024800,0xba493021,0x1c802080,0x1008e2ac}, {0x341004,0x841400e1,0x20000020,0x10149800,0x4aa70c2,0x54208688,0x4130c62,0x20109180,0x2064082,0x54001c40,0xe4e90383,0x84802125,0x2000e433,0xe60944c0,0x81260a03,0x80112da}, {0x97906901,0xf8864001,0x81e24d,0xa6510a0e,0x81ec011a,0x8441c600,0xb62cadb8,0x8741a46f,0x4b028d54,0x2681161,0x2057bb60,0x43350a0,0xb7b4a8c0,0x1122402,0x20009ad3,0xc82271}, {0x809e2081,0xe1800c8a,0x8151b009,0x40281031,0x89a52a0e,0x620e69b6,0xd1444425,0x4d548085,0x1fb12c75,0x862dd807,0x4841d87c,0x226e414e,0x9e088200,0xed37f80c,0x75268c80,0x8149313}, {0xc8040e32,0x6ea6484e,0x66702c4a,0xba0126c0,0x185dd30c,0,0,0,0,0x5400000,0x81337020,0x3a54f81,0x641055ec,0x2344c318,0x341462,0x1a090a43}, {0x13a5187b,0xa8480102,0xc5440440,0xe2dd8106,0x2d481af0,0x416b626,0x6e405058,0x31128032,0xc0007e4,0x420a8208,0x803b4840,0x87134860,0x3428850d,0xe5290319,0x870a2345,0x5c1825a9}, {0xd9c577a6,0x3e85e00,0xa7000081,0x41c6cd54,0xa2042800,0x2b0ab860,0xda9e0020,0xe1a08ea,0x11c0427c,0x3768908,0x1058621,0x18a80000,0xc44846a0,0x20220d05,0x91485422,0x28978a01}, {0x87898,0x31221605,0x8804240,0x6a2fa4e,0x92110814,0x9b042002,0x6432e52,0x90105000,0x85ba0041,0x20203042,0x5a04f0b,0x40802708,0x1a930591,0x600df50,0x3021a202,0x4e800630}, {0x4c80cc4,0x8001a004,0xd4316000,0xa020880,0x281c00,0x418e18,0xca106ad0,0x4b00f210,0x1506274d,0x88900220,0x82a85a00,0x81504549,0x80002004,0x2c088804,0x508d1,0x4ac48001}, {0x62e020,0xa42008e,0x6a8c3055,0xe0a5090e,0x42c42906,0x80b34814,0xb330803e,0x731c0102,0x600d1494,0x9400c20,0xc040301a,0xc094a451,0x5c88dca,0xa40c96c2,0x34040001,0x11000c8}, {0xa9c9550d,0x1c5a2428,0x48370142,0x100f7a4d,0x452a32b4,0x9205317b,0x5c44b894,0x458a68d7,0x2ed15097,0x42081943,0x9d40d202,0x20979840,0x64d5409,0,0,0}, {0,0x84800000,0x4215542,0x17001c06,0x61107624,0xb9ddff87,0x5c0a659f,0x3c00245d,0x59adb0,0,0,0x9b28d0,0x2000422,0x44080108,0xac409804,0x90288d0a}, {0xe0018700,0x310400,0x82211794,0x10540019,0x21a2cb2,0x40039c02,0x88043d60,0x7900080c,0xba3c1628,0xcb088640,0x90807274,0x1e,0xd8000000,0x9c87e188,0x4124034,0x2791ae64}, {0xe6fbe86b,0x5366408f,0x537feea6,0xb5e4e32b,0x2869f,0x1228548,0x8004402,0x20a02116,0x2040004,0x52000,0x1547e00,0x1ac162c,0x10852a84,0x5308c14,0xb943fbc3,0x906000ca}, {0x40326000,0x80901200,0x4c810b30,0x40020054,0x1d6a0029,0x2802000,0x48000,0x150c2610,0x7018040,0xc24d94d,0x18502810,0x50205001,0x4d01000,0x2017080,0x21c30108,0x132}, {0x7190088,0x5600802,0x4c0e0012,0xf0a10405,0x2,0,0,0,0,0,0,0x800000,0x35a8e8d,0x5a0421bd,0x11703488,0x26}, {0x10000000,0x8804c502,0xf801b815,0x25ed147c,0x1bb0ed60,0x1bd70589,0x1a627af3,0xac50d0c,0x524ae5d1,0x63050490,0x52440354,0x16122b57,0x1101a872,0x182949,0x10080948,0x886c6000}, {0x58f916e,0x39903012,0x4930f840,0x1b8880,0,0x428500,0x98000058,0x7014ea04,0x611d1628,0x60005113,0xa71a24,0,0x3c00000,0x10187120,0xa9270172,0x89066004}, {0x20cc022,0x40810900,0x8ca0202d,0xe34,0,0x11012100,0xc11a8011,0x892ec4c,0x85000040,0x1806c7ac,0x512e03e,0x108000,0x80ce4008,0x2106d01,0x8568641,0x27011e}, {0x83d3750,0x4e05e032,0x48401c0,0x1400081,0,0,0,0x591aa0,0x882443c8,0xc8001d48,0x72030152,0x4049013,0x4008280,0xd148a10,0x2088056,0x2704a040}, {0x4c000000,0,0,0xa3200000,0xa0ae1902,0xdf002660,0x7b15f010,0x3ad08121,0x284180,0x48001003,0x8014cc00,0xc414cf,0x30202000,0x1,0,0}, {0,0,0,0,0,0,0,0,0xffffdf7a,0xefffffff,0x3fffffff,0,0,0,0,0x2} }; class LgiIso2022Jp { public: LgiIso2022Jp() { int n, o = 0, i = 0; for (n=0; n<3; n++) iso2022jp_map[o++] = &iso2022jp_blocks[i++]; o += 13; for (n=0; n<4; n++) iso2022jp_map[o++] = &iso2022jp_blocks[i++];; o += 4; iso2022jp_map[o++] = &iso2022jp_blocks[i++];; o += 14; for (n=0; n<41; n++) iso2022jp_map[o++] = &iso2022jp_blocks[i++];; o += 47; iso2022jp_map[o++] = &iso2022jp_blocks[i++];; LAssert(o == 128); } bool CanEncode(char16 *s, ssize_t l) { if (s) { if (l < 0) l = StrlenW(s); for (int i=0; i> 9]; if (!*block) { return false; } u &= 0x1ff; if ( ( *block[u >> 5] & (1 << (u & 0x1f)) ) == 0 ) { return false; } } return true; } return false; } } Iso2022Jp; ///////////////////////////////////////////////////////////////////////////////////// bool LIsUtf8(const char *s, ssize_t len) { #define LenCheck(Need) \ if (len >= 0 && (len - (s - Start)) < Need) \ goto Utf8Error; #define TrailCheck() \ if (!IsUtf8_Trail(*s)) \ goto Utf8Error; \ s++; if (!s || *s == 0) return true; const char *Start = s; while ( ( len < 0 || ((s - Start) < len) ) && *s ) { if (IsUtf8_1Byte(*s)) { s++; } else if (IsUtf8_2Byte(*s)) { s++; LenCheck(1); TrailCheck(); } else if (IsUtf8_3Byte(*s)) { s++; LenCheck(2); TrailCheck(); TrailCheck(); } else if (IsUtf8_4Byte(*s)) { s++; LenCheck(3); TrailCheck(); TrailCheck(); TrailCheck(); } else goto Utf8Error; } return true; Utf8Error: #if 1 LgiTrace("%s:%i - Invalid utf @ offset=%i, bytes=", _FL, (int) (s - Start)); auto end = len < 0 ? NULL : Start + len; for (auto i = 0; i < 16; i++) { if ( (end && s >= end) || *s == 0 ) break; LgiTrace("%02.2x,", (uint8_t)*s++); } LgiTrace("\n"); #endif return false; } ///////////////////////////////////////////////////////////////////////////////////// short _gdc_usascii_mapping[128] = { // 0x80 - 0x8f 0xc7, 0xfc, 0xe9, 0xe2, 0xe4, 0xe0, 0xe5, 0xe7, 0xea, 0xeb, 0xe8, 0xef, 0xee, 0xec, 0xc4, 0xc5, // 0x90 - 0x9f 0xc9, 0xe6, 0xc6, 0xf4, 0xf6, 0xf2, 0xfb, 0xf9, 0xff, 0xd6, 0xdc, 0xa2, 0xa3, 0xa5, 0x20a7, 0x192, // 0xa0 - 0xaf 0xe1, 0xed, 0xf3, 0xfa, 0xf1, 0xd1, 0xb2, 0xb0, 0xbf, 0x2310, 0xac, 0xbd, 0xbc, 0xa1, 0xab, 0xbb, // 0xb0 - 0xbf 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510, // 0xc0 - 0xcf 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567, // 0xd0 - 0xdf 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580, // 0xe0 - 0xef 0x3b1, 0x3b2, 0x393, 0x3a0, 0x3a3, 0x3c3, 0x3bc, 0x3c4, 0x3a6, 0x398, 0x3a9, 0x3b4, 0x221e, 0xd8, 0x3b6, 0x2229, // 0xf0 - 0xff 0x2261, 0xb1, 0x2265, 0x2264, 0x2320, 0x2321, 0xf7, 0x2248, 0xb0, 0x2022, 0x2219, 0x221a, 0x207f, 178, 0x25a0, 0x25a1 }; // This mapping just NUL's out the characters between 0x80 and 0x9f which aren't defined // in the ISO spec. The rest of the characters map to themselves. short _gdc_ISO_8859_identity_mapping[128] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff }; short _gdc_ISO_8859_2_mapping[128] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xa0, 0x104, 0x2d8, 0x141, 0xa4, 0x13d, 0x15a, 0xa7, 0xa8, 0x160, 0x15e, 0x164, 0x179, 0xad, 0x17d, 0x17b, 0xb0, 0x105, 0x2db, 0x142, 0xb4, 0x13e, 0x15b, 0x2c7, 0xb8, 0x161, 0x15f, 0x165, 0x17a, 0x2dd, 0x17e, 0x17c, 0x154, 0xc1, 0xc2, 0x102, 0xc4, 0x139, 0x106, 0xc7, 0x10c, 0xc9, 0x118, 0xcb, 0x11a, 0xcd, 0xce, 0x10e, 0x110, 0x143, 0x147, 0xd3, 0xd4, 0x150, 0xd6, 0xd7, 0x158, 0x16e, 0xda, 0x170, 0xdc, 0xdd, 0x162, 0xdf, 0x155, 0xe1, 0xe2, 0x103, 0xe4, 0x13a, 0x107, 0xe7, 0x10d, 0xe9, 0x119, 0xeb, 0x11b, 0xed, 0xee, 0x10f, 0x111, 0x144, 0x148, 0xf3, 0xf4, 0x151, 0xf6, 0xf7, 0x159, 0x16f, 0xfa, 0x171, 0xfc, 0xfd, 0x163, 0x2d9 }; short _gdc_ISO_8859_3_mapping[128] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xa0, 0x126, 0x2d8, 0xa3, 0xa4, 0, 0x124, 0xa7, 0xa8, 0x130, 0x15e, 0x11e, 0x134, 0xad, 0, 0x17b, 0xb0, 0x127, 0xb2, 0xb3, 0xb4, 0xb5, 0x125, 0xb7, 0xb8, 0x131, 0x15f, 0x11f, 0x135, 0xbd, 0, 0x17c, 0xc0, 0xc1, 0xc2, 0, 0xc4, 0x10a, 0x108, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0, 0xd1, 0xd2, 0xd3, 0xd4, 0x120, 0xd6, 0xd7, 0x11c, 0xd9, 0xda, 0xdb, 0xdc, 0x16c, 0x15c, 0xdf, 0xe0, 0xe1, 0xe2, 0, 0xe4, 0x10b, 0x109, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0, 0xf1, 0xf2, 0xf3, 0xf4, 0x121, 0xf6, 0xf7, 0x11d, 0xf9, 0xfa, 0xfb, 0xfc, 0x16d, 0x15d, 0x2d9 }; short _gdc_ISO_8859_4_mapping[128] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x0A0, 0x104, 0x138, 0x156, 0x0A4, 0x128, 0x13B, 0x0A7, 0x0A8, 0x160, 0x112, 0x122, 0x166, 0x0AD, 0x17D, 0x0AF, 0x0B0, 0x105, 0x2DB, 0x157, 0x0B4, 0x129, 0x13C, 0x2C7, 0x0B8, 0x161, 0x113, 0x123, 0x167, 0x14A, 0x17E, 0x14B, 0x100, 0x0C1, 0x0C2, 0x0C3, 0x0C4, 0x0C5, 0x0C6, 0x12E, 0x10C, 0x0C9, 0x118, 0x0CB, 0x116, 0x0CD, 0x0CE, 0x12A, 0x110, 0x145, 0x14C, 0x136, 0x0D4, 0x0D5, 0x0D6, 0x0D7, 0x0D8, 0x172, 0x0DA, 0x0DB, 0x0DC, 0x168, 0x16A, 0x0DF, 0x101, 0x0E1, 0x0E2, 0x0E3, 0x0E4, 0x0E5, 0x0E6, 0x12F, 0x10D, 0x0E9, 0x119, 0x0EB, 0x117, 0x0ED, 0x0EE, 0x12B, 0x111, 0x146, 0x14D, 0x137, 0x0F4, 0x0F5, 0x0F6, 0x0F7, 0x0F8, 0x173, 0x0FA, 0x0FB, 0x0FC, 0x169, 0x16B, 0x2D9 }; short _gdc_ISO_8859_5_mapping[128] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x0A0, 0x401, 0x402, 0x403, 0x404, 0x405, 0x406, 0x407, 0x408, 0x409, 0x40A, 0x40B, 0x40C, 0x0AD, 0x40E, 0x40F, 0x410, 0x411, 0x412, 0x413, 0x414, 0x415, 0x416, 0x417, 0x418, 0x419, 0x41A, 0x41B, 0x41C, 0x41D, 0x41E, 0x41F, 0x420, 0x421, 0x422, 0x423, 0x424, 0x425, 0x426, 0x427, 0x428, 0x429, 0x42A, 0x42B, 0x42C, 0x42D, 0x42E, 0x42F, 0x430, 0x431, 0x432, 0x433, 0x434, 0x435, 0x436, 0x437, 0x438, 0x439, 0x43A, 0x43B, 0x43C, 0x43D, 0x43E, 0x43F, 0x440, 0x441, 0x442, 0x443, 0x444, 0x445, 0x446, 0x447, 0x448, 0x449, 0x44A, 0x44B, 0x44C, 0x44D, 0x44E, 0x44F, 0x2116, 0x451, 0x452, 0x453, 0x454, 0x455, 0x456, 0x457, 0x458, 0x459, 0x45A, 0x45B, 0x45C, 0x0A7, 0x45E, 0x45F }; short _gdc_ISO_8859_6_mapping[128] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xA0, 0, 0, 0, 0xA4, 0, 0, 0, 0, 0, 0, 0, 0x60C, 0xAD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x61B, 0, 0, 0, 0x61F, 0, 0x621, 0x622, 0x623, 0x624, 0x625, 0x626, 0x627, 0x628, 0x629, 0x62A, 0x62B, 0x62C, 0x62D, 0x62E, 0x62F, 0x630, 0x631, 0x632, 0x633, 0x634, 0x635, 0x636, 0x637, 0x638, 0x639, 0x63A, 0, 0, 0, 0, 0, 0x640, 0x641, 0x642, 0x643, 0x644, 0x645, 0x646, 0x647, 0x648, 0x649, 0x64A, 0x64B, 0x64C, 0x64D, 0x64E, 0x64F, 0x650, 0x651, 0x652, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; short _gdc_ISO_8859_7_mapping[128] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x0A0, 0x2BD, 0x2BC, 0x0A3, 0, 0, 0x0A6, 0x0A7, 0x0A8, 0x0A9, 0, 0x0AB, 0x0AC, 0x0AD, 0, 0x015, 0x0B0, 0x0B1, 0x0B2, 0x0B3, 0x384, 0x385, 0x386, 0x0B7, 0x388, 0x389, 0x38A, 0x0BB, 0x38C, 0x0BD, 0x38E, 0x38F, 0x390, 0x391, 0x392, 0x393, 0x394, 0x395, 0x396, 0x397, 0x398, 0x399, 0x39A, 0x39B, 0x39C, 0x39D, 0x39E, 0x39F, 0x3A0, 0x3A1, 0, 0x3A3, 0x3A4, 0x3A5, 0x3A6, 0x3A7, 0x3A8, 0x3A9, 0x3AA, 0x3AB, 0x3AC, 0x3AD, 0x3AE, 0x3AF, 0x3B0, 0x3B1, 0x3B2, 0x3B3, 0x3B4, 0x3B5, 0x3B6, 0x3B7, 0x3B8, 0x3B9, 0x3BA, 0x3BB, 0x3BC, 0x3BD, 0x3BE, 0x3BF, 0x3C0, 0x3C1, 0x3C2, 0x3C3, 0x3C4, 0x3C5, 0x3C6, 0x3C7, 0x3C8, 0x3C9, 0x3CA, 0x3CB, 0x3CC, 0x3CD, 0x3CE, 0 }; short _gdc_ISO_8859_8_mapping[128] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x0A0, 0, 0x0A2, 0x0A3, 0x0A4, 0x0A5, 0x0A6, 0x0A7, 0x0A8, 0x0A9, 0x0D7, 0x0AB, 0x0AC, 0x0AD, 0x0AE, 0x203E, 0x0B0, 0x0B1, 0x0B2, 0x0B3, 0x0B4, 0x0B5, 0x0B6, 0x0B7, 0x0B8, 0x0B9, 0x0F7, 0x0BB, 0x0BC, 0x0BD, 0x0BE, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2017, 0x5D0, 0x5D1, 0x5D2, 0x5D3, 0x5D4, 0x5D5, 0x5D6, 0x5D7, 0x5D8, 0x5D9, 0x5DA, 0x5DB, 0x5DC, 0x5DD, 0x5DE, 0x5DF, 0x5E0, 0x5E1, 0x5E2, 0x5E3, 0x5E4, 0x5E5, 0x5E6, 0x5E7, 0x5E8, 0x5E9, 0x5EA, 0, 0, 0, 0, 0 }; short _gdc_ISO_8859_9_mapping[128] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0x11E, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0x130, 0x15E, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0x11F, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0x131, 0x15F, 0xFF }; short _gdc_ISO_8859_13_mapping[128] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xA0, 0x201D, 0xA2, 0xA3, 0xA4, 0x201E, 0xA6, 0xA7, 0xD8, 0xA9, 0x156, 0xAB, 0xAC, 0xAD, 0xAE, 0xC6, 0xB0, 0xB1, 0xB2, 0xB3, 0x201C, 0xB5, 0xB6, 0xB7, 0xF8, 0xB9, 0x157, 0xBB, 0xBC, 0xBD, 0xBE, 0xE6, 0x104, 0x12E, 0x100, 0x106, 0xC4, 0xC5, 0x118, 0x112, 0x10C, 0xC9, 0x179, 0x116, 0x122, 0x136, 0x12A, 0x13B, 0x160, 0x143, 0x145, 0xD3, 0x14C, 0xD5, 0xD6, 0xD7, 0x172, 0x141, 0x15A, 0x16A, 0xDC, 0x17B, 0x17D, 0xDF, 0x105, 0x12F, 0x101, 0x107, 0xE4, 0xE5, 0x119, 0x113, 0x10D, 0xE9, 0x17A, 0x117, 0x123, 0x137, 0x12B, 0x13C, 0x161, 0x144, 0x146, 0xF3, 0x14D, 0xF5, 0xF6, 0xF7, 0x173, 0x142, 0x15B, 0x16B, 0xFC, 0x17C, 0x17E, 0x2019 }; short _gdc_ISO_8859_15_mapping[128] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xA0, 0xA1, 0xA2, 0xA3, 0x20AC, 0xA5, 0x160, 0xA7, 0x161, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0x17D, 0xB5, 0xB6, 0xB7, 0x17E, 0xB9, 0xBA, 0xBB, 0x152, 0x153, 0x178, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF }; short _gdc_win_874_mapping[128] = { 0x20AC, 0, 0, 0, 0, 0x2026, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0, 0, 0, 0, 0, 0, 0, 0, 0xA0, 0xE01, 0xE02, 0xE03, 0xE04, 0xE05, 0xE06, 0xE07, 0xE08, 0xE09, 0xE0A, 0xE0B, 0xE0C, 0xE0D, 0xE0E, 0xE0F, 0xE10, 0xE11, 0xE12, 0xE13, 0xE14, 0xE15, 0xE16, 0xE17, 0xE18, 0xE19, 0xE1A, 0xE1B, 0xE1C, 0xE1D, 0xE1E, 0xE1F, 0xE20, 0xE21, 0xE22, 0xE23, 0xE24, 0xE25, 0xE26, 0xE27, 0xE28, 0xE29, 0xE2A, 0xE2B, 0xE2C, 0xE2D, 0xE2E, 0xE2F, 0xE30, 0xE31, 0xE32, 0xE33, 0xE34, 0xE35, 0xE36, 0xE37, 0xE38, 0xE39, 0xE3A, 0, 0, 0, 0, 0xE3F, 0xE40, 0xE41, 0xE42, 0xE43, 0xE44, 0xE45, 0xE46, 0xE47, 0xE48, 0xE49, 0xE4A, 0xE4B, 0xE4C, 0xE4D, 0xE4E, 0xE4F, 0xE50, 0xE51, 0xE52, 0xE53, 0xE54, 0xE55, 0xE56, 0xE57, 0xE58, 0xE59, 0xE5A, 0xE5B, 0, 0, 0, 0, }; short _gdc_win_1250_mapping[128] = { 0x20AC, 0, 0x201A, 0, 0x201E, 0x2026, 0x2020, 0x2021, 0, 0x2030, 0x0160, 0x2039, 0x015A, 0x0164, 0x017D, 0x0179, 0, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0, 0x2122, 0x0161, 0x203A, 0x015B, 0x0165, 0x017E, 0x017A, 0xA0, 0x02C7, 0x02D8, 0x0141, 0xA4, 0x0104, 0xA6, 0xA7, 0xA8, 0xA9, 0x015E, 0xAB, 0xAC, 0xAD, 0xAE, 0x017B, 0xB0, 0xB1, 0x02DB, 0x0142, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0x0105, 0x015F, 0xBB, 0x013D, 0x02DD, 0x013E, 0x017C, 0x0154, 0xC1, 0xC2, 0x0102, 0xC4, 0x0139, 0x0106, 0xC7, 0x010C, 0xC9, 0x0118, 0xCB, 0x011A, 0xCD, 0xCE, 0x010E, 0x0110, 0x0143, 0x0147, 0xD3, 0xD4, 0x0150, 0xD6, 0xD7, 0x0158, 0x016E, 0xDA, 0x0170, 0xDC, 0xDD, 0x0162, 0xDF, 0x0155, 0xE1, 0xE2, 0x0103, 0xE4, 0x013A, 0x0107, 0xE7, 0x010D, 0xE9, 0x0119, 0xEB, 0x011B, 0xED, 0xEE, 0x010F, 0x0111, 0x0144, 0x0148, 0xF3, 0xF4, 0x0151, 0xF6, 0xF7, 0x0159, 0x016F, 0xFA, 0x0171, 0xFC, 0xFD, 0x0163, 0x02D9 }; short _gdc_win_1251_mapping[128] = { 0x402, 0x403, 0x201A, 0x453, 0x201E, 0x2026, 0x2020, 0x2021, 0x20AC, 0x2030, 0x409, 0x2039, 0x40A, 0x40C, 0x40B, 0x40F, 0x452, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0, 0x2122, 0x459, 0x203A, 0x45A, 0x45C, 0x45B, 0x45F, 0x0A0, 0x40E, 0x45E, 0x408, 0x0A4, 0x490, 0x0A6, 0x0A7, 0x401, 0x0A9, 0x404, 0x0AB, 0x0AC, 0x0AD, 0x0AE, 0x407, 0x0B0, 0x0B1, 0x406, 0x456, 0x491, 0x0B5, 0x0B6, 0x0B7, 0x451, 0x2116, 0x454, 0x0BB, 0x458, 0x405, 0x455, 0x457, 0x410, 0x411, 0x412, 0x413, 0x414, 0x415, 0x416, 0x417, 0x418, 0x419, 0x41A, 0x41B, 0x41C, 0x41D, 0x41E, 0x41F, 0x420, 0x421, 0x422, 0x423, 0x424, 0x425, 0x426, 0x427, 0x428, 0x429, 0x42A, 0x42B, 0x42C, 0x42D, 0x42E, 0x42F, 0x430, 0x431, 0x432, 0x433, 0x434, 0x435, 0x436, 0x437, 0x438, 0x439, 0x43A, 0x43B, 0x43C, 0x43D, 0x43E, 0x43F, 0x440, 0x441, 0x442, 0x443, 0x444, 0x445, 0x446, 0x447, 0x448, 0x449, 0x44A, 0x44B, 0x44C, 0x44D, 0x44E, 0x44F }; short _gdc_win_1252_mapping[128] = { 0x20AC, 0, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0, 0x017D, 0, 0, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0, 0x017E, 0x0178, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF }; short _gdc_win_1253_mapping[128] = { 0x20AC, 0, 0x201A, 0x192, 0x201E, 0x2026, 0x2020, 0x2021, 0, 0x2030, 0, 0x2039, 0, 0, 0, 0, 0, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0, 0x2122, 0, 0x203A, 0, 0, 0, 0, 0xA0, 0x385, 0x386, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0, 0xAB, 0xAC, 0xAD, 0xAE, 0x2015, 0xB0, 0xB1, 0xB2, 0xB3, 0x384, 0xB5, 0xB6, 0xB7, 0x388, 0x389, 0x38A, 0xBB, 0x38C, 0xBD, 0x38E, 0x38F, 0x390, 0x391, 0x392, 0x393, 0x394, 0x395, 0x396, 0x397, 0x398, 0x399, 0x39A, 0x39B, 0x39C, 0x39D, 0x39E, 0x39F, 0x3A0, 0x3A1, 0, 0x3A3, 0x3A4, 0x3A5, 0x3A6, 0x3A7, 0x3A8, 0x3A9, 0x3AA, 0x3AB, 0x3AC, 0x3AD, 0x3AE, 0x3AF, 0x3B0, 0x3B1, 0x3B2, 0x3B3, 0x3B4, 0x3B5, 0x3B6, 0x3B7, 0x3B8, 0x3B9, 0x3BA, 0x3BB, 0x3BC, 0x3BD, 0x3BE, 0x3BF, 0x3C0, 0x3C1, 0x3C2, 0x3C3, 0x3C4, 0x3C5, 0x3C6, 0x3C7, 0x3C8, 0x3C9, 0x3CA, 0x3CB, 0x3CC, 0x3CD, 0x3CE, 0 }; short _gdc_win_1254_mapping[128] = { 0x20AC, 0, 0x201A, 0x192, 0x201E, 0x2026, 0x2020, 0x2021, 0x2C6, 0x2030, 0x160, 0x2039, 0x152, 0, 0, 0, 0, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x2DC, 0x2122, 0x161, 0x203A, 0x153, 0, 0, 0x178, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0x11E, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0x130, 0x15E, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0x11F, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0x131, 0x15F, 0xFF, }; short _gdc_win_1255_mapping[128] = { 0x20AC, 0, 0x201A, 0x192, 0x201E, 0x2026, 0x2020, 0x2021, 0x2C6, 0x2030, 0, 0x2039, 0, 0, 0, 0, 0, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x2DC, 0x2122, 0, 0x203A, 0, 0, 0, 0, 0xA0, 0xA1, 0xA2, 0xA3, 0x20AA, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xD7, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xF7, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0x5B0, 0x5B1, 0x5B2, 0x5B3, 0x5B4, 0x5B5, 0x5B6, 0x5B7, 0x5B8, 0x5B9, 0, 0x5BB, 0x5BC, 0x5BD, 0x5BE, 0x5BF, 0x5C0, 0x5C1, 0x5C2, 0x5C3, 0x5F0, 0x5F1, 0x5F2, 0x5F3, 0x5F4, 0, 0, 0, 0, 0, 0, 0, 0x5D0, 0x5D1, 0x5D2, 0x5D3, 0x5D4, 0x5D5, 0x5D6, 0x5D7, 0x5D8, 0x5D9, 0x5DA, 0x5DB, 0x5DC, 0x5DD, 0x5DE, 0x5DF, 0x5E0, 0x5E1, 0x5E2, 0x5E3, 0x5E4, 0x5E5, 0x5E6, 0x5E7, 0x5E8, 0x5E9, 0x5EA, 0, 0, 0x200E, 0x200F, 0, }; short _gdc_win_1256_mapping[128] = { 0x20AC, 0x67E, 0x201A, 0x192, 0x201E, 0x2026, 0x2020, 0x2021, 0x2C6, 0x2030, 0x679, 0x2039, 0x152, 0x686, 0x698, 0x688, 0x6AF, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x6A9, 0x2122, 0x691, 0x203A, 0x153, 0x200C, 0x200D, 0x6BA, 0xA0, 0x60C, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0x6BE, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0x61B, 0xBB, 0xBC, 0xBD, 0xBE, 0x61F, 0x6C1, 0x621, 0x622, 0x623, 0x624, 0x625, 0x626, 0x627, 0x628, 0x629, 0x62A, 0x62B, 0x62C, 0x62D, 0x62E, 0x62F, 0x630, 0x631, 0x632, 0x633, 0x634, 0x635, 0x636, 0xD7, 0x637, 0x638, 0x639, 0x63A, 0x640, 0x641, 0x642, 0x643, 0xE0, 0x644, 0xE2, 0x645, 0x646, 0x647, 0x648, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0x649, 0x64A, 0xEE, 0xEF, 0x64B, 0x64C, 0x64D, 0x64E, 0xF4, 0x64F, 0x650, 0xF7, 0x651, 0xF9, 0x652, 0xFB, 0xFC, 0x200E, 0x200F, 0x6D2, }; short _gdc_win_1257_mapping[128] = { 0x20AC, 0, 0x201A, 0, 0x201E, 0x2026, 0x2020, 0x2021, 0, 0x2030, 0, 0x2039, 0, 0xA8, 0x2C7, 0xB8, 0, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0, 0x2122, 0, 0x203A, 0, 0xAF, 0x2DB, 0, 0xA0, 0, 0xA2, 0xA3, 0xA4, 0, 0xA6, 0xA7, 0xD8, 0xA9, 0x156, 0xAB, 0xAC, 0xAD, 0xAE, 0xC6, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xF8, 0xB9, 0x157, 0xBB, 0xBC, 0xBD, 0xBE, 0xE6, 0x104, 0x12E, 0x100, 0x106, 0xC4, 0xC5, 0x118, 0x112, 0x10C, 0xC9, 0x179, 0x116, 0x122, 0x136, 0x12A, 0x13B, 0x160, 0x143, 0x145, 0xD3, 0x14C, 0xD5, 0xD6, 0xD7, 0x172, 0x141, 0x15A, 0x16A, 0xDC, 0x17B, 0x17D, 0xDF, 0x105, 0x12F, 0x101, 0x107, 0xE4, 0xE5, 0x119, 0x113, 0x10D, 0xE9, 0x17A, 0x117, 0x123, 0x137, 0x12B, 0x13C, 0x161, 0x144, 0x146, 0xF3, 0x14D, 0xF5, 0xF6, 0xF7, 0x173, 0x142, 0x15B, 0x16B, 0xFC, 0x17C, 0x17E, 0x2D9, }; short _gdc_win_1258_mapping[128] = { 0x20AC, 0, 0x201A, 0x192, 0x201E, 0x2026, 0x2020, 0x2021, 0x2C6, 0x2030, 0, 0x2039, 0x152, 0, 0, 0, 0, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x2DC, 0x2122, 0, 0x203A, 0x153, 0, 0, 0x178, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0x102, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0x300, 0xCD, 0xCE, 0xCF, 0x110, 0xD1, 0x309, 0xD3, 0xD4, 0x1A0, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0x1AF, 0x303, 0xDF, 0xE0, 0xE1, 0xE2, 0x103, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0x301, 0xED, 0xEE, 0xEF, 0x111, 0xF1, 0x323, 0xF3, 0xF4, 0x1A1, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0x1B0, 0x20AB, 0xFF, }; short _gdc_koi8r_mapping[128] = { 0x2500, 0x2502, 0x250C, 0x2510, 0x2514, 0x2518, 0x251C, 0x2524, 0x252C, 0x2534, 0x253C, 0x2580, 0x2584, 0x2588, 0x258C, 0x2590, 0x2591, 0x2592, 0x2593, 0x2320, 0x25A0, 0x2219, 0x221A, 0x2248, 0x2264, 0x2265, 0xA0, 0x2321, 0xB0, 0xB2, 0xB7, 0xF7, 0x2550, 0x2551, 0x2552, 0x451, 0x2553, 0x2554, 0x2555, 0x2556, 0x2557, 0x2558, 0x2559, 0x255A, 0x255B, 0x255C, 0x255D, 0x255E, 0x255F, 0x2560, 0x2561, 0x401, 0x2562, 0x2563, 0x2564, 0x2565, 0x2566, 0x2567, 0x2568, 0x2569, 0x256A, 0x256B, 0x256C, 0xA9, 0x44E, 0x430, 0x431, 0x446, 0x434, 0x435, 0x444, 0x433, 0x445, 0x438, 0x439, 0x43A, 0x43B, 0x43C, 0x43D, 0x43E, 0x43F, 0x44F, 0x440, 0x441, 0x442, 0x443, 0x436, 0x432, 0x44C, 0x44B, 0x437, 0x448, 0x44D, 0x449, 0x447, 0x44A, 0x42E, 0x410, 0x411, 0x426, 0x414, 0x415, 0x424, 0x413, 0x425, 0x418, 0x419, 0x41A, 0x41B, 0x41C, 0x41D, 0x41E, 0x41F, 0x42F, 0x420, 0x421, 0x422, 0x423, 0x416, 0x412, 0x42C, 0x42B, 0x417, 0x428, 0x42D, 0x429, 0x427, 0x42A }; short _gdc_koi8u_mapping[128] = { 0x2500, 0x2502, 0x250C, 0x2510, 0x2514, 0x2518, 0x251C, 0x2524, 0x252C, 0x2534, 0x253C, 0x2580, 0x2584, 0x2588, 0x258C, 0x2590, 0x2591, 0x2592, 0x2593, 0x2320, 0x25A0, 0x2019, 0x221A, 0x2248, 0x2264, 0x2265, 0xA0, 0x2321, 0xB0, 0xB2, 0xB7, 0xF7, 0x2550, 0x2551, 0x2552, 0x451, 0x454, 0x2554, 0x456, 0x457, 0x2557, 0x2558, 0x2559, 0x255A, 0x255B, 0x491, 0x255D, 0x255E, 0x255F, 0x2560, 0x2561, 0x401, 0x404, 0x2563, 0x406, 0x407, 0x2566, 0x2567, 0x2568, 0x2569, 0x256A, 0x490, 0x256C, 0xA9, 0x44E, 0x430, 0x431, 0x446, 0x434, 0x435, 0x444, 0x433, 0x445, 0x438, 0x439, 0x43A, 0x43B, 0x43C, 0x43D, 0x43E, 0x43F, 0x44F, 0x440, 0x441, 0x442, 0x443, 0x436, 0x432, 0x44C, 0x44B, 0x437, 0x448, 0x44D, 0x449, 0x447, 0x44A, 0x42E, 0x410, 0x411, 0x426, 0x414, 0x415, 0x424, 0x413, 0x425, 0x418, 0x419, 0x41A, 0x41B, 0x41C, 0x41D, 0x41E, 0x41F, 0x42F, 0x420, 0x421, 0x422, 0x423, 0x416, 0x412, 0x42C, 0x42B, 0x417, 0x428, 0x42D, 0x429, 0x427, 0x42A }; short _gdc_koi8ru_mapping[128] = { 0x2500, 0x2502, 0x250C, 0x2510, 0x2514, 0x2518, 0x251C, 0x2524, 0x252C, 0x2534, 0x253C, 0x2580, 0x2584, 0x2588, 0x258C, 0x2590, 0x2591, 0x2592, 0x2593, 0x201C, 0x25A0, 0x2219, 0x201D, 0x2014, 0x2116, 0x2122, 0xA0, 0xBB, 0xAE, 0xAB, 0xB7, 0xA4, 0x2550, 0x2551, 0x2552, 0x451, 0x454, 0x2554, 0x456, 0x457, 0x2557, 0x2558, 0x2559, 0x255A, 0x255B, 0x491, 0x45E, 0x255E, 0x255F, 0x2560, 0x2561, 0x401, 0x404, 0x2563, 0x406, 0x407, 0x2566, 0x2567, 0x2568, 0x2569, 0x256A, 0x490, 0x40E, 0xA9, 0x44E, 0x430, 0x431, 0x446, 0x434, 0x435, 0x444, 0x433, 0x445, 0x438, 0x439, 0x43A, 0x43B, 0x43C, 0x43D, 0x43E, 0x43F, 0x44F, 0x440, 0x441, 0x442, 0x443, 0x436, 0x432, 0x44C, 0x44B, 0x437, 0x448, 0x44D, 0x449, 0x447, 0x44A, 0x42E, 0x410, 0x411, 0x426, 0x414, 0x415, 0x424, 0x413, 0x425, 0x418, 0x419, 0x41A, 0x41B, 0x41C, 0x41D, 0x41E, 0x41F, 0x42F, 0x420, 0x421, 0x422, 0x423, 0x416, 0x412, 0x42C, 0x42B, 0x417, 0x428, 0x42D, 0x429, 0x427, 0x42A, }; #if defined WIN32 #define WinDef(d) d // Map the windows codepages to their real names. #define WINDOWS_1250 EASTEUROPE_CHARSET #define WINDOWS_1252 ANSI_CHARSET #define WINDOWS_1251 RUSSIAN_CHARSET #define WINDOWS_1253 GREEK_CHARSET #define WINDOWS_1254 TURKISH_CHARSET #define WINDOWS_1255 HEBREW_CHARSET #define WINDOWS_1256 ARABIC_CHARSET #define WINDOWS_1257 BALTIC_CHARSET #define WINDOWS_932 SHIFTJIS_CHARSET #define WINDOWS_936 GB2313_CHARSET #define WINDOWS_949 HANGEUL_CHARSET #define WINDOWS_950 CHINESEBIG5_CHARSET #else #define WinDef(d) 0 #endif LCharset::LCharset(const char *cp, const char *des, short *map, const char *alt) { Charset = cp; Description = des; UnicodeMap = map; IconvName = 0; AlternateNames = alt; Type = CpNone; if (cp) { if (stricmp(cp, "utf-8") == 0) { Type = CpUtf8; } else if (stricmp(cp, "utf-16") == 0) { Type = CpUtf16; } else if (stricmp(cp, "utf-32") == 0) { Type = CpUtf32; } else if (stricmp(cp, "ucs-2") == 0) { Type = CpUtf16; IconvName = "UCS-2-INTERNAL"; } else if (UnicodeMap) { Type = CpMapped; } #ifdef WIN32 else if (strnicmp(cp, "windows-", 8) == 0) { Type = CpWindowsDb; } #endif else { Type = CpIconv; } } } bool LCharset::IsUnicode() { return (Type == CpUtf8) || (Type == CpUtf16) || (Type == CpUtf32); } const char *LCharset::GetIconvName() { return IconvName ? IconvName : Charset; } bool LCharset::IsAvailable() { if (Type != CpIconv) return true; #ifndef LGI_STATIC LFontSystem *FontSys = LFontSystem::Inst(); if (FontSys) return FontSys->HasIconv(true); #endif // LGI_STATIC return false; } static LCharset LgiCharsets[] = { // Good 'ol ascii LCharset("us-ascii", "ASCII", _gdc_usascii_mapping, "ascii-us,iso-ir-6,ANSI_X3.4-1986,ISO_646.irv,ASCII,ISO646-US,us,IBM367,cp367,csASCII"), // Unicode (up here so they get found first) LCharset("utf-8", "Utf-8"), LCharset("utf-16", "Utf-16"), LCharset("utf-32", "Utf-32"), LCharset("ucs-2", "Ucs-2"), // ISO (get prefered over windows charsets by being first in this list) LCharset("iso-8859-1", "ISO 8859-1 (West Europe)", _gdc_ISO_8859_identity_mapping, "iso-ir-100,ISO_8859-1,latin1,l1,IBM819,CP819,csISOLatin1,iso8859-1"), LCharset("iso-8859-2", "ISO 8859-2 (East Europe)", _gdc_ISO_8859_2_mapping, "iso-ir-101,ISO_8859-2,latin2,l2,csISOLatin2"), LCharset("iso-8859-3", "ISO 8859-3 (Latin Script)", _gdc_ISO_8859_3_mapping, "iso-ir-109,ISO_8859-3,latin3,l3,csISOLatin3"), LCharset("iso-8859-4", "ISO 8859-4 (Baltic)", _gdc_ISO_8859_4_mapping, "iso-ir-110,ISO_8859-4,latin4,l4,csISOLatin4"), LCharset("iso-8859-5", "ISO 8859-5 (Russian)", _gdc_ISO_8859_5_mapping, "iso-ir-144,ISO_8859-5,cyrillic,csISOLatinCyrillic"), LCharset("iso-8859-6", "ISO 8859-6 (Arabic)", _gdc_ISO_8859_6_mapping, "iso-ir-127,ISO_8859-6,ECMA-114,ASMO-708,arabic,csISOLatinArabic"), LCharset("iso-8859-7", "ISO 8859-7 (Greek)", _gdc_ISO_8859_7_mapping, "iso-ir-126,ISO_8859-7,ELOT_928,ECMA-118,greek,greek8,csISOLatinGreek"), LCharset("iso-8859-8", "ISO 8859-8 (Hebrew)", _gdc_ISO_8859_8_mapping, "iso-ir-138,ISO_8859-8,hebrew,csISOLatinHebrew,iso-8859-8-i"), LCharset("iso-8859-9", "ISO 8859-9 (Turkish)", _gdc_ISO_8859_9_mapping, "iso-ir-148,ISO_8859-9,latin5,l5,csISOLatin5"), LCharset("iso-8859-13", "ISO 8859-13 (Baltik)", _gdc_ISO_8859_13_mapping, "ISO_8859-9"), LCharset("iso-8859-15", "ISO 8859-15 (Latic 9)", _gdc_ISO_8859_15_mapping, "ISO_8859-15"), // Windows LCharset("windows-874", "Windows 874 (Thai)", _gdc_win_874_mapping, "iso-8859-11,cp874"), LCharset("windows-932", "Windows 932 (Japanese)"), LCharset("windows-936", "Windows 936 (Chinese)"), LCharset("windows-949", "Windows 949 (Korean)"), LCharset("windows-950", "Windows 950 (Chinese)"), LCharset("windows-1250", "Windows 1250 (Latin 2)", _gdc_win_1250_mapping, "x-cp1250,cp1250"), LCharset("windows-1251", "Windows 1251 (Cyrillic)", _gdc_win_1251_mapping, "x-cp1251,cp1251"), LCharset("windows-1252", "Windows 1252 (Latin 1)", _gdc_win_1252_mapping, "x-cp1252,cp1252"), LCharset("windows-1253", "Windows 1253 (Greek)", _gdc_win_1253_mapping, "x-cp1253,cp1253"), LCharset("windows-1254", "Windows 1254 (Turkish)", _gdc_win_1254_mapping, "x-cp1254,cp1254"), LCharset("windows-1255", "Windows 1255 (Hebrew)", _gdc_win_1255_mapping, "x-cp1255,cp1255"), LCharset("windows-1256", "Windows 1256 (Arabic)", _gdc_win_1256_mapping, "x-cp1256,cp1256"), LCharset("windows-1257", "Windows 1257 (Baltic)", _gdc_win_1257_mapping, "x-cp1257,cp1257"), LCharset("windows-1258", "Windows 1258 (Veitnam)", _gdc_win_1258_mapping, "x-cp1258,cp1258"), // Russian LCharset("koi8-r", "KOI8-R", _gdc_koi8r_mapping, "csKOI8R"), LCharset("koi8-u", "KOI8-U", _gdc_koi8u_mapping, "csKOI8U"), LCharset("koi8-ru", "KOI8-RU", _gdc_koi8ru_mapping, "csKOI8RU"), LCharset("koi8-t", "KOI8-T (Tajik)"), // Codepages LCharset("cp850", "Cp850", 0, "IBM850,850,csPC850Multilingual"), LCharset("cp862", "Cp862", 0, "IBM862,862,csPC862LatinHebrew"), LCharset("cp866", "Cp866", 0, "IBM866,866,csIBM866"), LCharset("cp1133", "Cp1133 (Laotian)"), // Japanese LCharset("euc-jp", "EUC-JP", 0, "csEUCPkdFmtJapanese"), LCharset("shift_jis", "SHIFT_JIS", 0, "MS_Kanji,csShiftJIS"), LCharset("cp932", "cp932", 0, 0), LCharset("iso-2022-jp", "ISO-2022-JP", 0, "csISO2022JP"), LCharset("iso-2022-jp-1", "ISO-2022-JP-1"), LCharset("iso-2022-jp-2", "ISO-2022-JP-2", 0, "csISO2022JP2"), // Chinese LCharset("euc-cn", "EUC-CN (Chinese)"), LCharset("hz-gb-2312", "HZ (Chinese)", 0, "hz"), LCharset("gbk", "GBK (Chinese)", 0, "CP936,MS936,windows-936,x-gbk,gb2312,GB-2312,csGB2312,GB2312_CHARSET"), LCharset("gb18030", "GB18030 (Chinese)"), LCharset("euc-tw", "EUC-TW (Chinese)"), LCharset("big5", "BIG5 (Chinese)", 0, "csBig5"), LCharset("big5-hkscs", "BIG5-HKSCS (Chinese)"), // LCharset("gb2312", "GB-2312 (Chinese)", 0, "GB-2312,csGB2312"), LCharset("iso-2022-cn", "ISO-2022-CN (Chinese)"), LCharset("iso-2022-cn-eXT","ISO-2022-CN-EXT (Chinese)"), // Korean LCharset("euc-kr", "EUC-KR", 0, "csEUCKR"), LCharset("iso-2022-kr", "ISO-2022-KR", 0, "csISO2022KR"), LCharset("johab", "JOHAB"), LCharset("cp949", "CP949", 0, "ks_c_5601-1987,ks_c_5601"), // Armenian // LCharset("armscii-8", "ARMSCII-8 (Armenian)"), // Georgian LCharset("Georgian-Academy","Georgian-Academy"), LCharset("Georgian-PS", " Georgian-PS"), // Thai LCharset("tis-620", "TIS-620 (Thai)"), // Laotian LCharset("mulelao-1", "MuleLao-1"), // Vietnamese LCharset("viscii", "VISCII (Vietnamese)", 0, "csVISCII"), LCharset("tcvn", "TCVN (Vietnamese)"), // EOF marker LCharset() }; static LCharsetSystem CharsetSystem; LCharsetSystem *LCharsetSystem::Inst() { return &CharsetSystem; } -LCharset *LGetCpInfo(const char *Cs) +LCharset *LGetCharsetInfo(const char *Cs) { return CharsetSystem.GetCsInfo(Cs); } ///////////////////////////////////////////////////////////////////////////// // Utf-16 conversion int LCpToAnsi(char *cp) { int Ansi = 0; if (cp && strnicmp(cp, "windows-", 8) == 0) { Ansi = atoi(cp+9); } return Ansi; } ssize_t LBufConvertCp(void *Out, const char *OutCp, ssize_t OutLen, const void *&In, const char *InCp, ssize_t &InLen) { int Status = 0; if (Out && OutCp && In && InCp) { - LCharset *InInfo = LGetCpInfo(InCp); - LCharset *OutInfo = LGetCpInfo(OutCp); + LCharset *InInfo = LGetCharsetInfo(InCp); + LCharset *OutInfo = LGetCharsetInfo(OutCp); if (InInfo && OutInfo) { char *In8 = (char*)In; uchar *Out8 = (uchar*)Out; if (InLen < 0) { switch (InInfo->Type) { case CpMapped: case CpUtf8: case CpIconv: InLen = (int)strlen((char*)In); break; case CpUtf16: case CpWindowsDb: InLen = StringLen((uint16*)In) << 1; break; case CpUtf32: InLen = StringLen((uint32_t*)In) << 2; break; default: LAssert(0); return 0; } } #ifdef WIN32 if (InInfo->Type == CpWindowsDb && OutInfo->Type == CpUtf16) { // mb -> unicode char Cp[32]; sprintf_s(Cp, sizeof(Cp), ".%s", InInfo->Charset + 8); setlocale(LC_ALL, Cp); void *Start = Out; while (OutLen >= sizeof(char16) && InLen > 0) { int s = mbtowc((char16*)Out, (char*)In, min(InLen, MB_CUR_MAX)); if (s > 0) { ((char*&)In) += s; InLen -= s; ((char16*&)Out)++; OutLen -= sizeof(char16); } else break; } return (NativeInt)Out-(NativeInt)Start; } else if (InInfo->Type == CpUtf16 && OutInfo->Type == CpWindowsDb) { // unicode -> mb char Cp[32]; sprintf_s(Cp, sizeof(Cp), ".%s", OutInfo->Charset + 8); setlocale(LC_ALL, Cp); void *Start = Out; while (OutLen >= MB_CUR_MAX && InLen > sizeof(char16) ) { #if 1 int s = 0; errno_t err = wctomb_s(&s, (char*)Out, OutLen, ((char16*)In)[0]); if (err || s == 0) break; #else int s = wctomb((char*)Out, ((char16*)In)[0] ); if (s > 0) #endif { ((char16*&)In)++; InLen -= sizeof(char16); ((char*&)Out) += s; OutLen -= s; } } return (NativeInt)Out-(NativeInt)Start; } else #endif if (InInfo->Type == CpIconv || OutInfo->Type == CpIconv) { #ifndef LGI_STATIC LFontSystem *Fs = LFontSystem::Inst(); if (Fs) return Fs->IconvConvert(OutInfo->GetIconvName(), (char*)Out, OutLen, InInfo->GetIconvName(), (const char*&)In, InLen); #else LAssert(!"No iconv in static build"); #endif } else { // Mapped or Utf conversion uint32_t Utf32 = 0; while (OutLen > 0 && InLen > 0) { char *RewindIn = In8; ptrdiff_t RewindInLen = InLen; // Convert input char to Utf-32 switch (InInfo->Type) { case CpMapped: { if (*In8) { uchar i = (uchar)*In8++; InLen--; if (i & 0x80) { Utf32 = InInfo->UnicodeMap[i - 0x80]; if (!Utf32) Utf32 = '?'; } else { Utf32 = i; } } else { Utf32 = 0; InLen = 0; } break; } case CpUtf8: { Utf32 = LgiUtf8To32((uint8_t *&)In8, InLen); break; } case CpUtf16: { Utf32 = LgiUtf16To32((const uint16_t *&)In8, InLen); if (Utf32 == 0xfeff || Utf32 == 0xfffe) continue; break; } case CpUtf32: { Utf32 = *((uint32_t*&)In8)++; InLen -= 4; break; } default: LAssert(0); break; } if (!Utf32) { break; } // Convert Utf-32 into output format switch (OutInfo->Type) { case CpMapped: { if (Utf32 & ~0x7f) { int n; for (n=0; n<128; n++) { if (OutInfo->UnicodeMap[n] == Utf32) { *Out8++ = 0x80 + n; break; } } if (n >= 128) { for (n=0; MissingMaps[n].Unicode; n++) { if (MissingMaps[n].Unicode == Utf32) { *Out8++ = MissingMaps[n].Ascii; break; } } if (!MissingMaps[n].Unicode) { *Out8++ = '?'; } } } else { *Out8++ = Utf32; } OutLen--; break; } case CpUtf8: { // uchar *PrevOut8 = Out8; if (!LgiUtf32To8(Utf32, (uint8_t*&) Out8, OutLen)) { // Not enough buffer to encode the character In8 = RewindIn; InLen = RewindInLen; OutLen = 0; } break; } case CpUtf16: { LgiUtf32To16(Utf32, (uint16_t*&)Out8, OutLen); break; } case CpUtf32: { *((uint32_t*&)Out8)++ = Utf32; OutLen -= 4; break; } default: { break; } } } In = (void*)In8; Status = (int) (Out8 - (uchar*)Out); } } else { // printf("%s:%i - LBufConvertCp failed '%s' -> '%s'.\n", __FILE__, __LINE__, InCp, OutCp); } } return Status; } template T *DupeString(T *s, ssize_t Len = -1) { if (!s) return NULL; if (Len < 0) { Len = 0; while (s[Len]) Len++; } T *ns = new T[Len+1]; if (!ns) return NULL; memcpy(ns, s, sizeof(T) * Len); ns[Len] = 0; return ns; } LString LStrConvertCp(const char *OutCp, const void *In, const char *InCp, ssize_t InLen) { if (!OutCp || !In || !InCp) return LString(); - LCharset *InInfo = LGetCpInfo(InCp); - LCharset *OutInfo = LGetCpInfo(OutCp); + LCharset *InInfo = LGetCharsetInfo(InCp); + LCharset *OutInfo = LGetCharsetInfo(OutCp); if (!InInfo || !OutInfo) return LString(); if (InLen < 0) { switch (InInfo->Type) { case CpMapped: case CpUtf8: case CpIconv: InLen = (int)strlen((char*)In); break; case CpUtf16: case CpWindowsDb: InLen = StringLen((uint16*)In) << 1; break; case CpUtf32: InLen = StringLen((uint32_t*)In) << 2; break; default: return LString(); } } switch (OutInfo->Type) { case CpMapped: case CpUtf8: case CpIconv: break; case CpUtf16: case CpWindowsDb: case CpUtf32: default: LAssert(!"LString doesn't >8bit char (yet)."); return LString(); } if (!stricmp(InCp, OutCp)) return LString((char*)In, InLen); LStringPipe b; if (InInfo->Type == CpIconv || OutInfo->Type == CpIconv) { #ifndef LGI_STATIC LFontSystem *Fs = LFontSystem::Inst(); if (Fs) { auto InCs = InInfo->GetIconvName(); auto OutCs = OutInfo->GetIconvName(); if (Fs->IconvConvert(OutCs, &b, InCs, (const char*&)In, InLen)) return b.NewLStr(); InCp = "iso-8859-1"; } #else LAssert(!"No inconv in static build"); #endif } char Buf[2 << 10]; while (InLen > 0) { ssize_t Bytes = LBufConvertCp(Buf, OutCp, sizeof(Buf), In, InCp, InLen); if (Bytes > 0) b.Write((uchar*)Buf, (int)Bytes); else break; } return b.NewLStr(); } -void *LNewConvertCp(const char *OutCp, const void *In, const char *InCp, ssize_t InLen) +void *LNewConvertCp(const char *OutCharset, const void *In, const char *InCharset, ssize_t InLen) { - if (!OutCp || !In || !InCp) + if (!OutCharset || !In || !InCharset) return NULL; - LCharset *InInfo = LGetCpInfo(InCp); - LCharset *OutInfo = LGetCpInfo(OutCp); + auto InInfo = LGetCharsetInfo(InCharset); + auto OutInfo = LGetCharsetInfo(OutCharset); if (!InInfo || !OutInfo) return NULL; LMemQueue b; if (InLen < 0) { switch (InInfo->Type) { case CpMapped: case CpUtf8: case CpIconv: InLen = (int)strlen((char*)In); break; case CpUtf16: case CpWindowsDb: InLen = StringLen((uint16*)In) << 1; break; case CpUtf32: InLen = StringLen((uint32_t*)In) << 2; break; default: LAssert(0); return NULL; } } int NullSize; switch (OutInfo->Type) { case CpMapped: case CpUtf8: case CpIconv: NullSize = 1; break; case CpUtf16: case CpWindowsDb: NullSize = 2; break; case CpUtf32: NullSize = 4; break; default: LAssert(0); return NULL; } - if (!stricmp(InCp, OutCp)) + if (!stricmp(InCharset, OutCharset)) { if (InInfo->Type == CpUtf16) return DupeString((uint16*)In, InLen/sizeof(uint16)); else if (InInfo->Type == CpUtf32) return DupeString((uint32_t*)In, InLen/sizeof(uint32_t)); else return NewStr((char*)In, InLen); } if (InInfo->Type == CpIconv || OutInfo->Type == CpIconv) { #ifndef LGI_STATIC LFontSystem *Fs = LFontSystem::Inst(); if (Fs) { const char *InCs = InInfo->GetIconvName(); const char *OutCs = OutInfo->GetIconvName(); if (!Fs->IconvConvert(OutCs, &b, InCs, (const char*&)In, InLen)) { - InCp = "iso-8859-1"; + InCharset = "iso-8859-1"; goto BufConvert; } } #else LAssert(!"No inconv in static build"); #endif } else { BufConvert: char Buf[2 << 10]; while (InLen > 0) { - ssize_t Bytes = LBufConvertCp(Buf, OutCp, sizeof(Buf), In, InCp, InLen); + ssize_t Bytes = LBufConvertCp(Buf, OutCharset, sizeof(Buf), In, InCharset, InLen); if (Bytes > 0) { b.Write((uchar*)Buf, (int)Bytes); } else { break; } } } return b.GetSize() ? b.New(NullSize) : 0; } int LCharLen(const void *Str, const char *Cp, int Bytes) { if (Str && Cp) { - LCharset *InInfo = LGetCpInfo(Cp); + LCharset *InInfo = LGetCharsetInfo(Cp); if (InInfo) { switch (InInfo->Type) { default: case CpMapped: { return (int)strlen((char*)Str); } case CpUtf8: { uchar *s = (uchar*)Str; int Len = 0; if (Bytes > 0) { uchar *e = s + Bytes; while (*s && s < e) { LgiNextUtf8((char*&)s); Len++; } } else { while (*s) { LgiNextUtf8((char*&)s); Len++; } } return Len; } case CpUtf16: { return StringLen((uint16*)Str); } case CpUtf32: { return StringLen((uint32_t*)Str); } } } } return 0; } bool LIsCpImplemented(char *Cp) { - return LGetCpInfo(Cp) != 0; + return LGetCharsetInfo(Cp) != 0; } const char *LAnsiToLgiCp(int AnsiCodePage) { if (AnsiCodePage < 0) { #ifdef WIN32 AnsiCodePage = GetACP(); #else return "utf-8"; #endif } #define WinCp(i) case i: return "windows-" #i; switch (AnsiCodePage) { WinCp(874) WinCp(932) WinCp(936) WinCp(949) WinCp(950) WinCp(1250) WinCp(1251) WinCp(1252) WinCp(1253) WinCp(1254) WinCp(1255) WinCp(1256) WinCp(1257) WinCp(1258) case 20127: return "us-ascii"; case 28591: return "iso-8859-1"; case 28592: return "iso-8859-2"; case 28593: return "iso-8859-3"; case 28594: return "iso-8859-4"; case 28595: return "iso-8859-5"; case 28596: return "iso-8859-6"; case 28597: return "iso-8859-7"; case 28598: return "iso-8859-8"; case 28599: return "iso-8859-9"; case 28600: return "ISO-8859-10"; case 28605: return "ISO-8859-15"; case 50220: case 50221: return "iso-2022-jp"; case 51932: return "euc-jp"; case 51949: return "euc-kr"; case 65001: return "utf-8"; } #undef WinCp return 0; } char *LSeekUtf8(const char *Ptr, ssize_t D, char *Start) { uchar *p = (uchar*)Ptr; if (p) { if (D >= 0) { for (int i=0; i(uchar*)Start; i++) { p--; while (p>(uchar*)Start && IsUtf8_Trail(*p)) p--; } } else { // You must pass a start point to move backwards in // the utf-8 string, otherwise you can run off the // beginning of the array. LAssert(0); } } return (char*)p; } bool LMatchCharset(short *Map, char16 *Utf, bool &Has8Bit) { if (Map && Utf) { // Test Charset because we have a map of all the chars in it... char16 *c; for (c = Utf; *c; c++) { if (*c > 0x7f) { // Check char Has8Bit = true; int i; for (i=0; i<128; i++) { if (Map[i] == *c) break; } if (i >= 128) { // Char not found return false; } } } if (Has8Bit) { if (!*c) { return true; } } } return false; } const char *LUnicodeToCharset(const char *Utf8, ssize_t Len, List *Prefs) { const char *Status = "utf-8"; // The default.. LAutoWString Utf((char16*)LNewConvertCp(LGI_WideCharset, Utf8, "utf-8", Len)); if (Utf) { if (Prefs) { for (auto p: *Prefs) { LCharset *Cp = CharsetSystem.GetCsInfo(p); if (Cp && stricmp(Cp->Charset, "us-ascii") != 0 && Cp->UnicodeMap) { bool Has8Bit = false; if (LMatchCharset(Cp->UnicodeMap, Utf, Has8Bit)) { return Cp->Charset; } if (!Has8Bit) { return "us-ascii"; } } } } for (LCharset *Cp = LgiCharsets + 1; Cp->Charset; Cp++) { if (Cp->UnicodeMap) { bool Has8Bit = false; if (LMatchCharset(Cp->UnicodeMap, Utf, Has8Bit)) { return Cp->Charset; } if (!Has8Bit) { return "us-ascii"; } } } } return Status; } LString LToNativeCp(const char *In, ssize_t InLen) { const char *Cp = LAnsiToLgiCp(); LString s; #ifdef WIN32 - LCharset *CpInfo = LGetCpInfo(Cp); + LCharset *CpInfo = LGetCharsetInfo(Cp); if (!CpInfo || CpInfo->Type == CpWindowsDb) { if (In) { // Double byte charset conversion, don't rely on iconv // being around to do the conversion. setlocale(LC_ALL, ".ACP"); if (InLen < 0) InLen = strlen(In); LAutoWString Wide(Utf8ToWide(In, InLen)); if (Wide) { size_t Converted; auto Len = wcstombs_s(&Converted, NULL, 0, Wide, 0); if (s.Length(Len)) { wcstombs_s(&Converted, s.Get(), Len+1, Wide, Len+1); s.Get()[Len] = 0; } } } } #endif if (!s) s = LStrConvertCp(Cp, In, "utf-8", InLen); return s; } LString LFromNativeCp(const char *In, ssize_t InLen) { const char *Cp = LAnsiToLgiCp(); LString s; #ifdef WIN32 - LCharset *CpInfo = LGetCpInfo(Cp); + LCharset *CpInfo = LGetCharsetInfo(Cp); if (!CpInfo || CpInfo->Type == CpWindowsDb) { if (In) { // Double byte charset conversion, don't rely on iconv // being around to do the conversion. setlocale(LC_ALL, ".ACP"); if (InLen < 0) { #ifdef __GNUC__ // FIXME InLen = strlen(In); #else InLen = _mbstrlen(In); #endif } else { // Work out how many chars 'InLen' bytes is ssize_t Bytes = InLen; const char *i = In; int Chars = 0; while (*i && Bytes > 0) { int n = mblen(i, MB_CUR_MAX); if (n > 0) { Chars++; Bytes -= n; i += n; } else break; } InLen = Chars; } size_t Converted; size_t Len = mbstowcs_s(&Converted, NULL, 0, In, 0); if (Len) { LAutoWString Buf(new char16[Len+1]); if (Buf) { mbstowcs_s(&Converted, Buf, Len, In, Len); Buf[Len] = 0; s = Buf; } } } } #endif if (!s) s = LStrConvertCp("utf-8", In, Cp, InLen); return s; } /////////////////////////////////////////////////////////////////////////// struct LCharsetSystemPriv { LCharset *Utf8; LCharset *Utf16; LHashTbl, LCharset*> Charsets; LCharsetSystemPriv() : Charsets(512) { Utf8 = 0; Utf16 = 0; } }; LCharsetSystem::LCharsetSystem() { char l[256]; // Charset setup, store all the charset pointers // in a hash table for O(1) lookup. d = new LCharsetSystemPriv; LAssert(LgiCharsets->Charset != NULL); for (LCharset *Cs = LgiCharsets; Cs->Charset; Cs++) { strcpy_s(l, sizeof(l), Cs->Charset); #ifdef _MSC_VER _strlwr_s(l, sizeof(l)); #else strlwr(l); #endif if (!stricmp(l, "utf-8")) d->Utf8 = Cs; else if (!stricmp(l, "utf-16")) d->Utf16 = Cs; d->Charsets.Add(l, Cs); auto a = LString(Cs->AlternateNames).SplitDelimit(","); for (int n=0; nCharsets.Add(l, Cs); } } } LCharsetSystem::~LCharsetSystem() { DeleteObj(d); } LCharset *LCharsetSystem::GetCsInfo(const char *Cp) { if (Cp && d) { // Lookup the charset in the hash table char l[256]; strcpy_s(l, sizeof(l), Cp); #ifdef _MSC_VER _strlwr_s(l, sizeof(l)); #else strlwr(l); #endif if (!stricmp(l, "utf-8")) return d->Utf8; else if (!stricmp(l, "utf-16")) return d->Utf16; LCharset *Cs = (LCharset*) d->Charsets.Find(l); if (Cs) { return Cs; } else { // printf("%s:%i - No charset '%s' in font sub system.\n", __FILE__, __LINE__, l); // printf("Charsets=%i\n", Charsets->GetUsed()); } } return 0; } LCharset *LGetCsInfo(const char *Cs) { return CharsetSystem.GetCsInfo(Cs); } LCharset *LCharsetSystem::GetCsList() { return LgiCharsets; } LCharset *LGetCsList() { return LgiCharsets; } diff --git a/src/common/Net/Mail.cpp b/src/common/Net/Mail.cpp --- a/src/common/Net/Mail.cpp +++ b/src/common/Net/Mail.cpp @@ -1,2708 +1,2702 @@ /*hdr ** FILE: Mail.cpp ** AUTHOR: Matthew Allen ** DATE: 28/5/98 ** DESCRIPTION: Mail app ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ #include #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Mail.h" #include "lgi/common/Base64.h" #include "lgi/common/DateTime.h" #include "lgi/common/DocView.h" #include "lgi/common/Store3Defs.h" #include "lgi/common/LgiRes.h" #include "lgi/common/TextConvert.h" #include "lgi/common/Mime.h" #include "../Hash/md5/md5.h" const char *sTextPlain = "text/plain"; const char *sTextHtml = "text/html"; const char *sTextXml = "text/xml"; const char *sApplicationInternetExplorer = "application/internet-explorer"; const char sMultipartMixed[] = "multipart/mixed"; const char sMultipartEncrypted[] = "multipart/encrypted"; const char sMultipartSigned[] = "multipart/signed"; const char sMultipartAlternative[] = "multipart/alternative"; const char sMultipartRelated[] = "multipart/related"; const char sAppOctetStream[] = "application/octet-stream"; ////////////////////////////////////////////////////////////////////////////////////////////////// LogEntry::LogEntry(LColour col) { c = col; } bool LogEntry::Add(const char *t, ssize_t len) { if (!t) return false; if (len < 0) len = strlen(t); /* // Strip off any whitespace on the end of the line. while (len > 0 && strchr(" \t\r\n", t[len-1])) len--; */ LAutoWString w(Utf8ToWide(t, len)); if (!w) return false; size_t ch = StrlenW(w); return Txt.Add(w, ch); } bool Base64Str(LString &s) { LString b64; ssize_t Base64Len = BufferLen_BinTo64(s.Length()); if (!b64.Set(NULL, Base64Len)) return false; #ifdef _DEBUG ssize_t Ch = #endif ConvertBinaryToBase64(b64.Get(), b64.Length(), (uchar*)s.Get(), s.Length()); LAssert(Ch == b64.Length()); s = b64; return true; } bool UnBase64Str(LString &s) { LString Bin; ssize_t BinLen = BufferLen_64ToBin(s.Length()); if (!Bin.Set(NULL, BinLen)) return false; ssize_t Ch = ConvertBase64ToBinary((uchar*)Bin.Get(), Bin.Length(), s.Get(), s.Length()); LAssert(Ch <= (int)Bin.Length()); s = Bin; s.Get()[Ch] = 0; return true; } ////////////////////////////////////////////////////////////////////////////////////////////////// // returns the maximum length of the lines contained in the string int MaxLineLen(char *Text) { if (!Text) return false; int Max = 0; int i = 0; for (char *c = Text; *c; c++) { if (*c == '\r') { // return } else if (*c == '\n') { // eol Max = MAX(i, Max); i = 0; } else { // normal char i++; } } return Max; } bool IsDotLined(char *Text) { if (Text) { for (char *l = Text; l && *l; ) { if (l[0] == '.') { if (l[1] == '\n' || l[1] == 0) { return true; } } l = strchr(l, '\n'); if (l) l++; } } return false; } // Is s a valid non-whitespace string? bool ValidNonWSStr(const char *s) { if (s && *s) { while (*s && strchr(" \r\t\n", *s)) { s++; } if (*s) { return true; } } return false; } void TokeniseStrList(char *Str, List &Output, const char *Delim) { if (Str && Delim) { char *s = Str; while (*s) { while (*s && strchr(WhiteSpace, *s)) s++; char *e = s; for (; *e; e++) { if (strchr("\'\"", *e)) { // handle string constant char delim = *e++; e = strchr(e, delim); } else if (*e == '<') { e = strchr(e, '>'); } else { while (*e && *e != '<' && !IsWhiteSpace(*e) && !strchr(Delim, *e)) e++; } if (!e || !*e || strchr(Delim, *e)) { break; } } ssize_t Len = e ? e - s : strlen(s); if (Len > 0) { char *Temp = new char[Len+1]; if (Temp) { memcpy(Temp, s, Len); Temp[Len] = 0; Output.Insert(Temp); } } if (e) { s = e; for (; *s && strchr(Delim, *s); s++); } else break; } } } //////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// void DeNullText(char *in, ssize_t &len) { char *out = in; char *end = in + len; while (in < end) { if (*in) { *out++ = *in; } else { len--; } in++; } } ////////////////////////////////////////////////////////////////////////////// typedef char CharPair[2]; static CharPair Pairs[] = { {'<', '>'}, {'(', ')'}, {'\'', '\''}, {'\"', '\"'}, {0, 0}, }; struct MailAddrPart { LAutoString Part; bool Brackets; bool ValidEmail; LAutoString RemovePairs(char *Str, ssize_t Len, CharPair *Pairs) { char *s = Str; if (Len < 0) Len = strlen(s); while (*s && strchr(WhiteSpace, *s)) { s++; Len--; } if (!*s) return LAutoString(); // Get the end of the string... char *e = s; if (Len < 0) e += strlen(s); else e += Len; // Seek back over any trailing whitespace while (e > s && strchr(WhiteSpace, e[-1])) e--; for (CharPair *p = Pairs; (*p)[0]; p++) { if ((*p)[0] == *s && (*p)[1] == e[-1]) { s++; e--; if (s < e) { // reset search p = Pairs - 1; } else break; } } Len = e - s; if (Len < 0) return LAutoString(); return LAutoString(NewStr(s, Len)); } MailAddrPart(char *s, ssize_t len) { ValidEmail = false; Brackets = false; if (s) { if (len < 0) len = strlen(s); while (strchr(WhiteSpace, *s) && len > 0) { s++; len--; } Brackets = *s == '<'; Part = RemovePairs(s, len, Pairs); // ValidEmail = IsValidEmail(Part); } } int Score() { if (!Part) return 0; return (ValidEmail ? 1 : 0) + (Brackets ? 1 : 0); } }; int PartCmp(LAutoPtr *a, LAutoPtr *b) { return (*b)->Score() - (*a)->Score(); } bool IsAngleBrackets(LString &s) { if (s(0) == '<' && s(-1) == '>') return true; return false; } void DecodeAddrName(const char *Str, std::function Cb, const char *DefaultDomain) { if (!Str) return; LString s = Str; LString non; LString email; LString::Array a; auto startBracket = s.Find("<"); auto endBracket = s.Find(">", startBracket); if (startBracket >= 0 && endBracket >= 0) { // Keep the angle brackets for the time being... a.New() = s(0, startBracket) + s(++endBracket, -1); a.New() = s(startBracket, endBracket); } else a.New() = s; for (unsigned i=0; i"); } else { non += a[i]; } } if (!email) { a = s.SplitDelimit("()"); non.Empty(); for (unsigned i=0; i 0) { const char *ChSet = " \t\r\n\'\"<>"; do { non = non.Strip(ChSet); } while (non.Length() > 0 && strchr(ChSet, non(0))); } Cb(non, email.Strip()); } void DecodeAddrName(const char *Start, LAutoString &Name, LAutoString &Addr, const char *DefaultDomain) { DecodeAddrName(Start, [&Name, &Addr](LString n, LString a){ Name.Reset(NewStr(n)); Addr.Reset(NewStr(a)); }, DefaultDomain); } void DecodeAddrName(const char *Start, LString &Name, LString &Addr, const char *DefaultDomain) { DecodeAddrName(Start, [&Name, &Addr](LString n, LString a){ Name = n; Addr = a; }, DefaultDomain); } #if 0 struct LDecodeAddrNameTest { LDecodeAddrNameTest() { // Testing code char *Input[] = { "\"Sound&Secure@speedytechnical.com\" ", "\"@MM-Social Mailman List\" ", "'Matthew Allen (fret)' ", "Matthew Allen (fret) ", "\"'Matthew Allen'\" ", "Matthew Allen", "fret@memecode.com", "\"\" ", " (fret@memecode.com)", "Matthew Allen ", "\"Matthew, Allen\" (fret@memecode.com)", "Matt'hew Allen ", "john.omalley ", "Bankers' Association (ABA)", "'Amy's Mum' ", "\"Philip Doggett (JIRA)\" ", "\"group name\" ", NULL }; LAutoString Name, Addr; for (char **i = Input; *i; i++) { Name.Reset(); Addr.Reset(); DecodeAddrName(*i, Name, Addr, "name.com"); LgiTrace("N=%-#32s A=%-32s\n", Name, Addr); } int asd=0; } } DecodeAddrNameTest; #endif void StrCopyToEOL(char *d, char *s) { if (d && s) { while (*s && *s != '\r' && *s != '\n') { *d++ = *s++; } *d = 0; } } ////////////////////////////////////////////////////////////////////////////////////////////////// MailTransaction::MailTransaction() { Index = -1; Flags = 0; Status = false; Oversize = false; Stream = 0; UserData = 0; } MailTransaction::~MailTransaction() { } ////////////////////////////////////////////////////////////////////////////////////////////////// FileDescriptor::FileDescriptor() { Embeded = 0; Offset = 0; Size = 0; Data = 0; MimeType = 0; ContentId = 0; Lock = 0; OwnEmbeded = false; } FileDescriptor::FileDescriptor(LStreamI *embed, int64 offset, int64 size, char *name) { Embeded = embed; Offset = offset; Size = size; Data = 0; MimeType = 0; Lock = 0; ContentId = 0; OwnEmbeded = false; if (name) { Name(name); } } FileDescriptor::FileDescriptor(char *name) { Embeded = 0; Offset = 0; Size = 0; Data = 0; MimeType = 0; Lock = 0; ContentId = 0; OwnEmbeded = false; if (name) { Name(name); if (File.Open(name, O_READ)) { Size = File.GetSize(); File.Close(); } } } FileDescriptor::FileDescriptor(char *data, int64 len) { Embeded = 0; Offset = 0; MimeType = 0; Lock = 0; ContentId = 0; Size = len; OwnEmbeded = false; Data = data ? new uchar[(size_t)Size] : 0; if (Data) { memcpy(Data, data, (size_t)Size); } } FileDescriptor::~FileDescriptor() { if (OwnEmbeded) { DeleteObj(Embeded); } DeleteArray(MimeType); DeleteArray(ContentId); DeleteArray(Data); } void FileDescriptor::SetOwnEmbeded(bool i) { OwnEmbeded = i; } void FileDescriptor::SetLock(LMutex *l) { Lock = l; } LMutex *FileDescriptor::GetLock() { return Lock; } LStreamI *FileDescriptor::GotoObject() { if (Embeded) { Embeded->SetPos(Offset); return Embeded; } else if (Name() && File.Open(Name(), O_READ)) { return &File; } else if (Data && Size > 0) { DataStream.Reset(new LMemStream(Data, Size, false)); return DataStream; } return 0; } int FileDescriptor::Sizeof() { return (int)Size; } uchar *FileDescriptor::GetData() { return Data; } bool FileDescriptor::Decode(char *ContentType, char *ContentTransferEncoding, char *MimeData, int MimeDataLen) { bool Status = false; int Content = CONTENT_NONE; if (ContentType && ContentTransferEncoding) { // Content-Type: application/octet-stream; name="Scribe.opt" Content = CONTENT_OCTET_STREAM; if (strnistr(ContentTransferEncoding, "base64", 1000)) { Content = CONTENT_BASE64; } if (strnistr(ContentTransferEncoding, "quoted-printable", 1000)) { Content = CONTENT_QUOTED_PRINTABLE; } if (Content != CONTENT_NONE) { const char *NameKey = "name"; char *n = strnistr(ContentType, NameKey, 1000); if (n) { char *Equal = strchr(n, '='); if (Equal) { Equal++; while (*Equal && *Equal == '\"') { Equal++; } char *End = strchr(Equal, '\"'); if (End) { *End = 0; } Name(Equal); Status = true; } } } } if (Status && MimeData && MimeDataLen > 0 && Content != CONTENT_NONE) { Status = false; char *Base64 = new char[MimeDataLen]; switch (Content) { case CONTENT_OCTET_STREAM: { Size = 0; DeleteObj(Data); Data = new uchar[MimeDataLen]; if (Data) { Size = MimeDataLen; memcpy(Data, MimeData, (size_t)Size); Status = true; } break; } case CONTENT_QUOTED_PRINTABLE: { Size = 0; DeleteObj(Data); Data = new uchar[MimeDataLen+1]; if (Data) { char *Out = (char*) Data; for (int i=0; i= Size - 3; if (Status) { Size = Converted; } else { DeleteArray(Data); Size = 0; } } break; } } } return Status; } ////////////////////////////////////////////////////////////////////////////////////////////////// AddressDescriptor::AddressDescriptor(const AddressDescriptor *Copy) { if (Copy) { Status = Copy->Status; CC = Copy->CC; sAddr = Copy->sAddr; sName = Copy->sName; } } AddressDescriptor::~AddressDescriptor() { _Delete(); } void AddressDescriptor::_Delete() { Status = false; CC = MAIL_ADDR_CC; sName.Empty(); sAddr.Empty(); } LString AddressDescriptor::Print() { LString s; char delim = '\''; if (sName) { bool hasSingle = sName.Find("\'") >= 0; bool hasDouble = sName.Find("\"") >= 0; if (hasSingle && !hasDouble) delim = '\"'; } if (sAddr && sName) s.Printf("%c%s%c <%s>", delim, sAddr.Get(), delim, sName.Get()); else if (sAddr) s.Printf("<%s>", sAddr.Get()); else if (sName) s.Printf("%c%s%c", delim, sName.Get(), delim); return s; } ////////////////////////////////////////////////////////////////////////////////////////////////// MailProtocol::MailProtocol() : SocketLock("MailProtocol") { Buffer[0] = 0; Logger = 0; ErrMsgId = 0; SettingStore = NULL; Items = 0; Transfer = 0; } MailProtocol::~MailProtocol() { CharsetPrefs.DeleteArrays(); } void MailProtocol::Log(const char *Str, LSocketI::SocketMsgType type) { if (Logger && Str) { char s[1024]; char *e = s + sizeof(s) - 2; const char *i = Str; char *o = s; while (*i && o < e) { *o++ = *i++; } while (o > s && (o[-1] == '\r' || o[-1] == '\n')) o--; *o++ = '\n'; *o = 0; Logger->Write(s, o - s, type); } } bool MailProtocol::Error(const char *file, int line, const char *msg, ...) { char s[1024]; va_list a; va_start(a, msg); vsprintf_s(s, sizeof(s), msg, a); va_end(a); Log(s, LSocketI::SocketMsgError); LgiTrace("%s:%i - Error: %s", file, line, s); return false; } bool MailProtocol::Read() { bool Status = false; if (Socket) { Status = Socket->Read(Buffer, sizeof(Buffer), 0) > 0; } return Status; } bool MailProtocol::Write(const char *Buf, bool LogWrite) { bool Status = false; if (Socket) { const char *p = Buf ? Buf : Buffer; Status = Socket->Write(p, strlen(p), 0) > 0; if (LogWrite) { Log(p, LSocketI::SocketMsgSend); } } return Status; } ////////////////////////////////////////////////////////////////////////////////////////////////// #define VERIFY_RET_VAL(Arg) \ { \ if (!Arg) \ { \ LMutex::Auto Lck(&SocketLock, _FL); \ Socket.Reset(0); \ return NULL; \ } \ } #define VERIFY_ONERR(Arg) \ { \ if (!Arg) \ { \ LMutex::Auto Lck(&SocketLock, _FL); \ Socket.Reset(0); \ goto CleanUp; \ } \ } void Reorder(LArray &a, const char *s) { for (unsigned i=0; i 0) { a.DeleteAt(i, true); a.AddAt(0, s); break; } } } MailSmtp::MailSmtp() { } MailSmtp::~MailSmtp() { } bool MailSmtp::Open(LSocketI *S, const char *RemoteHost, const char *LocalDomain, const char *UserName, const char *Password, int Port, int Flags) { char Str[256] = ""; bool Status = false; if (!RemoteHost) Error(_FL, "No remote SMTP host.\n"); else { strcpy_s(Str, sizeof(Str), RemoteHost); char *Colon = strchr(Str, ':'); if (Colon) { *Colon = 0; Colon++; Port = atoi(Colon); } if (Port == 0) { if (Flags & MAIL_SSL) Port = SMTP_SSL_PORT; else Port = SMTP_PORT; } LAutoString Server(TrimStr(Str)); if (Server) { if (SocketLock.Lock(_FL)) { Socket.Reset(S); SocketLock.Unlock(); } Socket->SetTimeout(30 * 1000); char Msg[256]; sprintf_s(Msg, sizeof(Msg), "Connecting to %s:%i...", Server.Get(), Port); Log(Msg, LSocketI::SocketMsgInfo); if (!Socket->Open(Server, Port)) Error(_FL, "Failed to connect socket to %s:%i\n", Server.Get(), Port); else { LStringPipe Str; // receive signon message VERIFY_RET_VAL(ReadReply("220")); // Rfc 2554 ESMTP authentication SmtpHello: sprintf_s(Buffer, sizeof(Buffer), "EHLO %s\r\n", (ValidNonWSStr(LocalDomain)) ? LocalDomain : "default"); VERIFY_RET_VAL(Write(0, true)); /*bool HasSmtpExtensions =*/ ReadReply("250", &Str); bool Authed = false; bool NoAuthTypes = false; bool SupportsStartTLS = false; LArray AuthTypes; // Look through the response for the auth line LString Response = Str.NewLStr(); if (Response) { auto Lines = Response.SplitDelimit("\n"); for (auto &l: Lines) { char *AuthStr = stristr(l, "AUTH"); if (AuthStr) { // walk through AUTH types auto Types = LString(AuthStr + 4).SplitDelimit(" ,;"); for (auto &t: Types) AuthTypes.Add(t); } if (stristr(l, "STARTTLS")) SupportsStartTLS = true; } } if (SupportsStartTLS && TestFlag(Flags, MAIL_USE_STARTTLS)) { strcpy_s(Buffer, sizeof(Buffer), "STARTTLS\r\n"); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply("220", &Str)); LVariant v; if (Socket->SetValue(LSocket_Protocol, v="SSL")) { Flags &= ~MAIL_USE_STARTTLS; goto SmtpHello; } else { // SSL init failed... what to do here? return false; } } if (TestFlag(Flags, MAIL_USE_AUTH)) { if (!ValidStr(UserName)) { // We need a user name in all authentication types. SetError(L_ERROR_ESMTP_NO_USERNAME, "No username for authentication."); return false; } if (AuthTypes.Length() == 0) { // No auth types? huh? if (TestFlag(Flags, MAIL_USE_PLAIN)) // Force plain type AuthTypes.Add("PLAIN"); else if (TestFlag(Flags, MAIL_USE_LOGIN)) // Force login type AuthTypes.Add("LOGIN"); else if (TestFlag(Flags, MAIL_USE_CRAM_MD5)) // Force CRAM MD5 type AuthTypes.Add("CRAM-MD5"); else if (TestFlag(Flags, MAIL_USE_OAUTH2)) // Force OAUTH2 type AuthTypes.Add("XOAUTH2"); else { // Try all AuthTypes.Add("PLAIN"); AuthTypes.Add("LOGIN"); AuthTypes.Add("CRAM-MD5"); AuthTypes.Add("XOAUTH2"); } } else { // Force user preference if (TestFlag(Flags, MAIL_USE_PLAIN)) Reorder(AuthTypes, "PLAIN"); else if (TestFlag(Flags, MAIL_USE_LOGIN)) Reorder(AuthTypes, "LOGIN"); else if (TestFlag(Flags, MAIL_USE_CRAM_MD5)) Reorder(AuthTypes, "CRAM-MD5"); else if (TestFlag(Flags, MAIL_USE_OAUTH2)) Reorder(AuthTypes, "XOAUTH2"); } for (auto Auth : AuthTypes) { // Try all their auth types against our internally support types if (Auth.Equals("LOGIN")) { VERIFY_RET_VAL(Write("AUTH LOGIN\r\n", true)); VERIFY_RET_VAL(ReadReply("334")); ZeroObj(Buffer); ConvertBinaryToBase64(Buffer, sizeof(Buffer), (uchar*)UserName, strlen(UserName)); strcat(Buffer, "\r\n"); VERIFY_RET_VAL(Write(0, true)); if (ReadReply("334") && Password) { ZeroObj(Buffer); ConvertBinaryToBase64(Buffer, sizeof(Buffer), (uchar*)Password, strlen(Password)); strcat(Buffer, "\r\n"); VERIFY_RET_VAL(Write(0, true)); if (ReadReply("235")) Authed = true; } } else if (Auth.Equals("PLAIN")) { char Ascii[512]; int ch = sprintf_s(Ascii, sizeof(Ascii), "%c%s%c%s", 0, UserName, 0, Password); char Base64[512] = {0}; ConvertBinaryToBase64(Base64, sizeof(Base64), (uint8_t*)Ascii, ch); sprintf_s(Buffer, sizeof(Buffer), "AUTH PLAIN %s\r\n", Base64); VERIFY_RET_VAL(Write(0, true)); if (ReadReply("235")) { Authed = true; } } else if (Auth.Equals("CRAM-MD5")) { sprintf_s(Buffer, sizeof(Buffer), "AUTH CRAM-MD5\r\n"); VERIFY_RET_VAL(Write(0, true)); if (ReadReply("334")) { auto Sp = strchr(Buffer, ' '); if (Sp) { Sp++; // Decode the server response: uint8_t Txt[128]; auto InLen = strlen(Sp); ssize_t TxtLen = ConvertBase64ToBinary(Txt, sizeof(Txt), Sp, InLen); // Calc the hash: // https://tools.ietf.org/html/rfc2104 char Key[64] = {0}; memcpy(Key, Password, MIN(strlen(Password), sizeof(Key))); uint8_t iKey[256]; char oKey[256]; for (unsigned i=0; i<64; i++) { iKey[i] = Key[i] ^ 0x36; oKey[i] = Key[i] ^ 0x5c; } memcpy(iKey+64, Txt, TxtLen); md5_state_t md5; md5_init(&md5); md5_append(&md5, iKey, 64 + TxtLen); md5_finish(&md5, oKey + 64); md5_init(&md5); md5_append(&md5, (uint8_t*)oKey, 64 + 16); char digest[16]; md5_finish(&md5, digest); char r[256]; int ch = sprintf_s(r, sizeof(r), "%s ", UserName); for (unsigned i=0; i<16; i++) ch += sprintf_s(r+ch, sizeof(r)-ch, "%02x", (uint8_t)digest[i]); // Base64 encode ssize_t Len = ConvertBinaryToBase64(Buffer, sizeof(Buffer), (uint8_t*)r, ch); Buffer[Len++] = '\r'; Buffer[Len++] = '\n'; Buffer[Len++] = 0; VERIFY_RET_VAL(Write(0, true)); if (ReadReply("235")) Authed = true; } } } else if (Auth.Equals("XOAUTH2")) { auto Log = dynamic_cast(Socket->GetLog()); LOAuth2 Authenticator(OAuth2, UserName, SettingStore, Socket->GetCancel(), Log); auto Tok = Authenticator.GetAccessToken(); if (Tok) { LString s; s.Printf("user=%s\001auth=Bearer %s\001\001\0", UserName, Tok.Get()); Base64Str(s); sprintf_s(Buffer, sizeof(Buffer), "AUTH %s %s\r\n", Auth.Get(), s.Get()); VERIFY_RET_VAL(Write(0, true)); Authed = ReadReply("235"); if (!Authed) { Authenticator.Refresh(); } } } else { LgiTrace("%s:%i - Unsupported auth type '%s'\n", _FL, Auth.Get()); } if (Authed) break; } if (!Authed) { if (NoAuthTypes) SetError(L_ERROR_ESMTP_NO_AUTHS, "The server didn't return the authentication methods it supports."); else { LString p; for (auto i : AuthTypes) { if (p.Get()) p += ", "; p += i; } SetError(L_ERROR_UNSUPPORTED_AUTH, "Authentication failed, types available:\n\t%s", p); } } Status = Authed; } else { Status = true; } } } } return Status; } bool MailSmtp::WriteText(const char *Str) { // we have to convert all strings to CRLF in here bool Status = false; if (Str) { LMemQueue Temp; const char *Start = Str; while (*Str) { if (*Str == '\n') { // send a string ssize_t Size = Str-Start; if (Str[-1] == '\r') Size--; Temp.Write((uchar*) Start, Size); Temp.Write((uchar*) "\r\n", 2); Start = Str + 1; } Str++; } // send the final string ssize_t Size = Str-Start; if (Str[-1] == '\r') Size--; Temp.Write((uchar*) Start, (int)Size); Size = (int)Temp.GetSize(); char *Data = new char[(size_t)Size]; if (Data) { Temp.Read((uchar*) Data, Size); Status = Socket->Write(Data, (int)Size, 0) == Size; DeleteArray(Data); } } return Status; } void StripChars(LString &s) { s = s.Strip("\r\n"); } char *CreateAddressTag(List &l, int Type, List *CharsetPrefs) { char *Result = 0; List Addr; for (auto a: l) { if (a->CC == Type) { Addr.Insert(a); } } if (Addr.Length() > 0) { LStringPipe StrBuf; StrBuf.Push((Type == 0) ? (char*)"To: " : (char*)"Cc: "); for (auto It = Addr.begin(); It != Addr.end(); ) { auto a = *It; AddressDescriptor *NextA = *(++It); char Buffer[256] = ""; StripChars(a->sName); StripChars(a->sAddr); if (a->sAddr && strchr(a->sAddr, ',')) { // Multiple address format auto t = a->sAddr.SplitDelimit(","); for (uint32_t i=0; i", t[i].Get()); if (i < t.Length()-1) strcat(Buffer, ",\r\n\t"); StrBuf.Push(Buffer); Buffer[0] = 0; } } else if (a->sName) { // Name and addr - char *Mem = 0; - char *Name = a->sName.Get(); - + auto Name = a->sName; if (Is8Bit(Name)) - { - Name = Mem = EncodeRfc2047(NewStr(Name), 0, CharsetPrefs); - } + Name = LEncodeRfc2047(Name, NULL/*charset*/, CharsetPrefs); if (strchr(Name, '\"')) - sprintf_s(Buffer, sizeof(Buffer), "'%s' <%s>", Name, a->sAddr.Get()); + sprintf_s(Buffer, sizeof(Buffer), "'%s' <%s>", Name.Get(), a->sAddr.Get()); else - sprintf_s(Buffer, sizeof(Buffer), "\"%s\" <%s>", Name, a->sAddr.Get()); - - DeleteArray(Mem); + sprintf_s(Buffer, sizeof(Buffer), "\"%s\" <%s>", Name.Get(), a->sAddr.Get()); } else if (a->sAddr) { // Just addr sprintf_s(Buffer, sizeof(Buffer), "<%s>", a->sAddr.Get()); } if (NextA) strcat(Buffer, ",\r\n\t"); StrBuf.Push(Buffer); a = NextA; } StrBuf.Push("\r\n"); Result = StrBuf.NewStr(); } return Result; } // This class implements a pipe that writes to a socket class SocketPipe : public LStringPipe { LSocketI *s; MailProtocolProgress *p; public: bool Status; SocketPipe(LSocketI *socket, MailProtocolProgress *progress) { s = socket; p = progress; Status = true; } ssize_t Read(void *Ptr, ssize_t Size, int Flags) { return false; } int64 SetSize(int64 Size) { if (p) { p->Start = LCurrentTime(); p->Range = (int)Size; return Size; } return -1; } ssize_t Write(const void *InPtr, ssize_t Size, int Flags) { char *Ptr = (char*)InPtr; char *e = Ptr + Size; while (Ptr < e) { ssize_t w = s->Write(Ptr, e - Ptr, 0); if (w > 0) { Ptr += w; if (p && p->Range && w > 0) p->Value += w; } else break; } return Ptr - (char*)InPtr; } }; bool MailSmtp::SendToFrom(List &To, AddressDescriptor *From, MailProtocolError *Err) { bool AddrOk = false; if (To.Length() == 0) { ErrMsgId = L_ERROR_ESMTP_NO_RECIPIENT; ErrMsgFmt = "No recipients to send to."; ErrMsgParam.Empty(); LgiTrace("%s:%i - No recipients.\n", _FL); return false; } // send MAIL message if (From && ValidStr(From->sAddr)) { sprintf_s(Buffer, sizeof(Buffer), "MAIL FROM: <%s>\r\n", From->sAddr.Get()); } else { ErrMsgId = L_ERROR_ESMTP_NO_FROM; ErrMsgFmt = "No 'from' address in email."; ErrMsgParam.Empty(); LgiTrace("%s:%i - Invalid from '%s'.\n", _FL, From->sAddr.Get()); return false; } VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply("250", 0, Err)); // send RCPT message AddrOk = true; List::I Recip = To.begin(); for (AddressDescriptor *a = *Recip; a; a = *++Recip) { LString Addr = ValidStr(a->sAddr) ? a->sAddr : a->sName; if (ValidStr(Addr)) { auto Parts = Addr.SplitDelimit(","); for (auto p: Parts) { sprintf_s(Buffer, sizeof(Buffer), "RCPT TO: <%s>\r\n", p.Get()); VERIFY_RET_VAL(Write(0, true)); a->Status = ReadReply("25", 0, Err); AddrOk |= a->Status != 0; // at least one address is ok } } else if (Err) { ErrMsgId = L_ERROR_ESMTP_BAD_RECIPIENT; ErrMsgFmt = "Invalid recipient '%s'."; ErrMsgParam = Addr; } } return AddrOk; } LStringPipe *MailSmtp::SendData(MailProtocolError *Err) { // send DATA message sprintf_s(Buffer, sizeof(Buffer), "DATA\r\n"); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply("354", 0, Err)); return new SocketPipe(Socket, Transfer); } LStringPipe *MailSmtp::SendStart(List &To, AddressDescriptor *From, MailProtocolError *Err) { return SendToFrom(To, From, Err) ? SendData(Err) : NULL; } bool MailSmtp::SendEnd(LStringPipe *m) { bool Status = false; SocketPipe *Msg = dynamic_cast(m); if (Msg) { // send message terminator and receive reply if (Msg->Status && Msg->Write((void*)"\r\n.\r\n", 5, 0)) { Status = ReadReply("250"); } // else // just close the connection on them // so nothing gets sent } DeleteObj(m); return Status; } /* bool MailSmtp::Send(MailMessage *Msg, bool Mime) { bool Status = false; if (Socket && Msg) { LStringPipe *Sink = SendStart(Msg->To, Msg->From); if (Sink) { // setup a gui progress meter to send the email, // the length is just a guesstimate as we won't know the exact // size until we encode it all, and I don't want it hanging around // in memory at once, so we encode and send on the fly. int Length = 1024 + (Msg->GetBody() ? strlen(Msg->GetBody()) : 0); for (FileDescriptor *f=Msg->FileDesc.First(); f; f=Msg->FileDesc.Next()) { Length += f->Sizeof() * 4 / 3; } // encode and send message for transport Msg->Encode(*Sink, 0, this); Status = SendEnd(Sink); } } return Status; } */ bool MailSmtp::Close() { if (Socket) { // send QUIT message sprintf_s(Buffer, sizeof(Buffer), "QUIT\r\n"); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply("221")); LMutex::Auto Lock(&SocketLock, _FL); Socket.Reset(0); return true; } return false; } bool MailSmtp::ReadReply(const char *Str, LStringPipe *Pipe, MailProtocolError *Err) { bool Status = false; if (Socket && Str) { ssize_t Pos = 0; char *Start = Buffer; ZeroObj(Buffer); while (Pos < sizeof(Buffer)) { ssize_t Len = Socket->Read(Buffer+Pos, sizeof(Buffer)-Pos, 0); if (Len > 0) { char *Eol = strstr(Start, "\r\n"); while (Eol) { // wipe EOL chars *Eol++ = 0; *Eol++ = 0; // process if (Pipe) { if (Pipe->GetSize()) Pipe->Push("\n"); Pipe->Push(Start); } if (Start[3] == ' ') { // end of response if (!strncmp(Start, Str, strlen(Str))) { Status = true; } if (Err) { Err->Code = atoi(Start); char *Sp = strchr(Start, ' '); Err->ErrMsg = Sp ? Sp + 1 : Start; } // Log Log(Start, atoi(Start) >= 400 ? LSocketI::SocketMsgError : LSocketI::SocketMsgReceive); // exit loop Pos = sizeof(Buffer); break; } else { Log(Start, LSocketI::SocketMsgReceive); // more lines follow Start = Eol; Eol = strstr(Start, "\r\n"); } } Pos += Len; } else break; } if (!Status) { SetError(L_ERROR_GENERIC, "Error: %s", Buffer); } } return Status; } ////////////////////////////////////////////////////////////////////////////////////////////////// class Mail2Folder : public LStringPipe { char File[256]; LFile F; public: Mail2Folder(char *Path, List &To) { do { char n[32]; sprintf_s(n, sizeof(n), "%u.mail", LRand()); LMakePath(File, sizeof(File), Path, n); } while (LFileExists(File)); if (F.Open(File, O_WRITE)) { F.Print("Forward-Path: "); int i = 0; for (auto a: To) { a->Status = true; auto Addrs = a->sAddr.SplitDelimit(","); for (unsigned n=0; n", Addrs[n].Get()); } } F.Print("\r\n"); } } ~Mail2Folder() { F.Close(); } ssize_t Read(void *Buffer, ssize_t Size, int Flags = 0) { return F.Read(Buffer, Size, Flags); } ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0) { return F.Write(Buffer, Size, Flags); } }; class MailPostFolderPrivate { public: char *Path; MailPostFolderPrivate() { Path = 0; } ~MailPostFolderPrivate() { DeleteArray(Path); } }; MailSendFolder::MailSendFolder(char *Path) { d = new MailPostFolderPrivate; d->Path = NewStr(Path); } MailSendFolder::~MailSendFolder() { DeleteObj(d); } bool MailSendFolder::Open(LSocketI *S, const char *RemoteHost, const char *LocalDomain, const char *UserName, const char *Password, int Port, int Flags) { return LDirExists(d->Path); } bool MailSendFolder::Close() { return true; } LStringPipe *MailSendFolder::SendStart(List &To, AddressDescriptor *From, MailProtocolError *Err) { return new Mail2Folder(d->Path, To); } bool MailSendFolder::SendEnd(LStringPipe *Sink) { DeleteObj(Sink); return true; } ////////////////////////////////////////////////////////////////////////////////////////////////// class MailItem { public: char *File; bool Delete; MailItem(char *f) { File = NewStr(f); Delete = false; } ~MailItem() { DeleteArray(File); } }; class MailReceiveFolderPrivate { public: char *Path; List Mail; MailReceiveFolderPrivate() { Path = 0; } ~MailReceiveFolderPrivate() { DeleteArray(Path); Mail.DeleteObjects(); } void Empty() { for (auto m: Mail) { if (m->Delete) { FileDev->Delete(m->File, false); } } Mail.DeleteObjects(); } }; MailReceiveFolder::MailReceiveFolder(char *Path) { d = new MailReceiveFolderPrivate; d->Path = NewStr(Path); } MailReceiveFolder::~MailReceiveFolder() { DeleteObj(d); } bool MailReceiveFolder::Open(LSocketI *S, const char *RemoteHost, int Port, const char *User, const char *Password, LDom *SettingStore, int Flags) { // We don't use the socket so just free it here... DeleteObj(S); // Argument check if (!LDirExists(d->Path)) return false; LDirectory Dir; // Loop through files, looking for email for (int b = Dir.First(d->Path, LGI_ALL_FILES); b; b = Dir.Next()) { if (!Dir.IsDir()) { if (MatchStr("*.eml", Dir.GetName()) || MatchStr("*.mail", Dir.GetName())) { char p[300]; Dir.Path(p, sizeof(p)); d->Mail.Insert(new MailItem(p)); } } } return true; } bool MailReceiveFolder::Close() { d->Empty(); return true; } ssize_t MailReceiveFolder::GetMessages() { return d->Mail.Length(); } bool MailReceiveFolder::Receive(LArray &Trans, MailCallbacks *Callbacks) { bool Status = false; for (unsigned i=0; iStream) { t->Status = false; MailItem *m = d->Mail[t->Index]; if (m) { LFile i; if (i.Open(m->File, O_READ)) { LCopyStreamer c; if (c.Copy(&i, t->Stream)) { Status = t->Status = true; if (Callbacks && Callbacks->OnReceive) { Callbacks->OnReceive(t, Callbacks->CallbackData); } } } } } } return Status; } bool MailReceiveFolder::Delete(int Message) { MailItem *m = d->Mail[Message]; if (m) { m->Delete = true; return false; } return false; } int MailReceiveFolder::Sizeof(int Message) { MailItem *m = d->Mail[Message]; if (m) { return (int)LFileSize(m->File); } return 0; } bool MailReceiveFolder::GetUid(int Message, char *Id, int IdLen) { if (Id) { MailItem *m = d->Mail[Message]; if (m) { char *s = strrchr(m->File, DIR_CHAR); if (s++) { char *e = strchr(s, '.'); if (!e) e = s + strlen(s); ssize_t Len = e - s; memcpy(Id, s, Len); Id[Len] = 0; return true; } } } return false; } bool MailReceiveFolder::GetUidList(LString::Array &Id) { bool Status = false; for (int i=0; iMail.Length(); i++) { char Uid[256]; if (GetUid(i, Uid, sizeof(Uid))) { Status = true; Id.New() = Uid; } else { Status = false; break; } } return Status; } LString MailReceiveFolder::GetHeaders(int Message) { MailItem *m = d->Mail[Message]; if (!m) return NULL; LFile i; if (!i.Open(m->File, O_READ)) return NULL; LStringPipe o; LCopyStreamer c; LHtmlLinePrefix e("", false); if (!c.Copy(&i, &o, &e)) return NULL; return o.NewLStr(); } ////////////////////////////////////////////////////////////////////////////////////////////////// MailPop3::MailPop3() { End = "\r\n.\r\n"; Marker = End; Messages = -1; } MailPop3::~MailPop3() { } ssize_t MailPop3::GetMessages() { if (Messages < 0) { if (Socket && Socket->IsOpen()) { // see how many messages there are VERIFY_ONERR(Write("STAT\r\n", true)); VERIFY_ONERR(ReadReply()); Messages = GetInt(); } else LAssert(!"No socket to get message count."); } CleanUp: return Messages; } int MailPop3::GetInt() { char Buf[32]; char *Start = strchr(Buffer, ' '); if (Start) { Start++; char *End = strchr(Start, ' '); if (End) { int Len = (int) (End - Start); memcpy(Buf, Start, Len); Buf[Len] = 0; return atoi(Buf); } } return 0; } bool MailPop3::ReadReply() { bool Status = false; if (Socket) { ssize_t Pos = 0; ZeroObj(Buffer); do { ssize_t Result = Socket->Read(Buffer+Pos, sizeof(Buffer)-Pos, 0); if (Result <= 0) // an error? { // Leave the loop... break; } Pos += Result; } while ( !strstr(Buffer, "\r\n") && sizeof(Buffer)-Pos > 0); Status = (Buffer[0] == '+') && strstr(Buffer, "\r\n"); char *Cr = strchr(Buffer, '\r'); if (Cr) *Cr = 0; if (ValidStr(Buffer)) Log(Buffer, (Status) ? LSocketI::SocketMsgReceive : LSocketI::SocketMsgError); if (Cr) *Cr = '\r'; if (!Status) { SetError(L_ERROR_GENERIC, "Error: %s", Buffer); } } return Status; } bool MailPop3::ListCmd(const char *Cmd, LHashTbl, bool> &Results) { sprintf_s(Buffer, sizeof(Buffer), "%s\r\n", Cmd); if (!Write(0, true)) return false; char *b = Buffer; ssize_t r; while ((r = Socket->Read(b, sizeof(Buffer)-(b-Buffer))) > 0) { b += r; if (Strnstr(Buffer, "\r\n.\r\n", b-Buffer)) break; } if (r <= 0) return false; auto Lines = LString(Buffer).SplitDelimit("\r\n"); for (unsigned i=1; iGetValue("IsSSL", IsSsl) && IsSsl.CastInt32()) Port = POP3_SSL_PORT; else Port = POP3_PORT; } strcpy_s(Str, sizeof(Str), RemoteHost); char *Colon = strchr(Str, ':'); if (Colon) { *Colon = 0; Colon++; Port = atoi(Colon); } if (S && User && Password && (Server = TrimStr(Str))) { S->SetTimeout(30 * 1000); ReStartConnection: if (SocketLock.Lock(_FL)) { Socket.Reset(S); SocketLock.Unlock(); } if (Socket && Socket->Open(Server, Port) && ReadReply()) { LVariant NoAPOP = false; if (SettingStore) SettingStore->GetValue(OPT_Pop3NoApop, NoAPOP); if (!NoAPOP.CastInt32()) { char *s = strchr(Buffer + 3, '<'); if (s) { char *e = strchr(s + 1, '>'); if (e) { Apop = NewStr(s, e - s + 1); } } } // login bool Authed = false; char *user = (char*) LNewConvertCp("iso-8859-1", User, "utf-8"); char *pass = (char*) LNewConvertCp("iso-8859-1", Password, "utf-8"); if (user && (pass || SecureAuth)) { bool SecurityError = false; if (TestFlag(Flags, MAIL_USE_STARTTLS)) { strcpy_s(Buffer, sizeof(Buffer), "STARTTLS\r\n"); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply()); LVariant v; if (Socket->SetValue(LSocket_Protocol, v="SSL")) { Flags &= ~MAIL_USE_STARTTLS; } else { SecurityError = true; } } if (!SecurityError && Apop) // GotKey, not implemented { // using encrypted password unsigned char Digest[16]; char HexDigest[33]; // append password char Key[256]; sprintf_s(Key, sizeof(Key), "%s%s", Apop, pass); ZeroObj(Digest); MDStringToDigest(Digest, Key); for (int i = 0; i < 16; i++) sprintf_s(HexDigest + (i*2), 3, "%2.2x", Digest[i]); HexDigest[32] = 0; sprintf_s(Buffer, sizeof(Buffer), "APOP %s %s\r\n", user, HexDigest); VERIFY_ONERR(Write(0, true)); Authed = ReadReply(); if (!Authed) { DeleteArray(Apop); LVariant NoAPOP = true; if (SettingStore) SettingStore->SetValue(OPT_Pop3NoApop, NoAPOP); S->Close(); goto ReStartConnection; } } if (!SecurityError && SecureAuth) { LHashTbl, bool> AuthTypes, Capabilities; if (ListCmd("AUTH", AuthTypes) && ListCmd("CAPA", Capabilities)) { if (AuthTypes.Find("GSSAPI")) { sprintf_s(Buffer, sizeof(Buffer), "AUTH GSSAPI\r\n"); VERIFY_ONERR(Write(0, true)); VERIFY_ONERR(ReadReply()); // http://www.faqs.org/rfcs/rfc2743.html } } } else if (!SecurityError && !Authed) { // have to use non-key method sprintf_s(Buffer, sizeof(Buffer), "USER %s\r\n", user); VERIFY_ONERR(Write(0, true)); VERIFY_ONERR(ReadReply()); sprintf_s(Buffer, sizeof(Buffer), "PASS %s\r\n", pass); VERIFY_ONERR(Write(0, false)); Log("PASS *******", LSocketI::SocketMsgSend); Authed = ReadReply(); } DeleteArray(user); DeleteArray(pass); } if (Authed) { Status = true; } else { if (SocketLock.Lock(_FL)) { Socket.Reset(0); SocketLock.Unlock(); } LgiTrace("%s:%i - Failed auth.\n", _FL); } } else Error(_FL, "Failed to open socket to %s:%i and read reply.\n", Server, Port); } else Error(_FL, "No user/pass.\n"); } CleanUp: DeleteArray(Apop); DeleteArray(Server); return Status; } bool MailPop3::MailIsEnd(LString &s) { ssize_t Len = s.Length(); for (auto c = s.Get(); c && Len-- > 0; c++) { if (*c != *Marker) { Marker = End; } if (*c == *Marker) { Marker++; if (!*Marker) { return true; } } } return false; } bool MailPop3::Receive(LArray &Trans, MailCallbacks *Callbacks) { bool Status = false; if (Trans.Length() > 0 && Socket) { for (unsigned n = 0; nIndex; LStreamI *Msg = Trans[n]->Stream; if (Msg) { int Size = 0; // Transfer is not null when the caller wants info on the bytes comming in if (Transfer || Callbacks) { // get message size sprintf_s(Buffer, sizeof(Buffer), "LIST %i\r\n", Message + 1); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply()); char *s = strchr(Buffer, ' '); if (s) { s = strchr(s+1, ' '); if (s) { Size = atoi(s); } } } MailSrcStatus Action = DownloadAll; int TopLines = 100; if (Callbacks && Callbacks->OnSrc) { Action = Callbacks->OnSrc(Trans[n], Size, &TopLines, Callbacks->CallbackData); } if (Action == DownloadAbort) { break; } if (Action == DownloadAll || Action == DownloadTop) { if (Action == DownloadAll) { sprintf_s(Buffer, sizeof(Buffer), "RETR %i\r\n", Message + 1); } else { sprintf_s(Buffer, sizeof(Buffer), "TOP %i %i\r\n", Message + 1, TopLines); } VERIFY_RET_VAL(Write(0, true)); LHtmlLinePrefix End(".\r\n"); if (Transfer) { Transfer->Value = 0; Transfer->Range = Size; Transfer->Start = LCurrentTime(); } // Read status line ZeroObj(Buffer); ssize_t Used = 0; bool Ok = false; bool Finished = false; int64 DataPos = 0; while (Socket->IsOpen()) { ssize_t r = Socket->Read(Buffer+Used, sizeof(Buffer)-Used-1, 0); if (r > 0) { DeNullText(Buffer + Used, r); if (Transfer) { Transfer->Value += r; } char *Eol = strchr(Buffer, '\n'); if (Eol) { Eol++; Ok = Buffer[0] == '+'; if (Ok) { // Log(Buffer, LSocketI::SocketMsgReceive); // The Buffer was zero'd at the beginning garrenteeing // NULL termination size_t Len = strlen(Eol); ssize_t EndPos = End.IsEnd(Eol, Len); if (EndPos >= 0) { Msg->Write(Eol, EndPos - 3); Status = Trans[n]->Status = true; Finished = true; } else { Msg->Write(Eol, Len); DataPos += Len; } } else { Log(Buffer, LSocketI::SocketMsgError); Finished = true; } break; } Used += r; } else break; } if (!Finished) { if (Ok) { // Read rest of message while (Socket->IsOpen()) { ssize_t r = Socket->Read(Buffer, sizeof(Buffer), 0); if (r > 0) { DeNullText(Buffer, r); if (Transfer) { Transfer->Value += r; } ssize_t EndPos = End.IsEnd(Buffer, r); if (EndPos >= 0) { ssize_t Actual = EndPos - DataPos - 3; if (Actual > 0) { #ifdef _DEBUG ssize_t w = #endif Msg->Write(Buffer, Actual); LAssert(w == Actual); } // else the end point was in the last buffer Status = Trans[n]->Status = true; break; } else { #ifdef _DEBUG ssize_t w = #endif Msg->Write(Buffer, r); LAssert(w == r); DataPos += r; } } else { break; } } if (!Status) { LgiTrace("%s:%i - Didn't get end-of-mail marker.\n", _FL); } } else { LgiTrace("%s:%i - Didn't get Ok.\n", _FL); break; } } if (Callbacks && Callbacks->OnReceive) { Callbacks->OnReceive(Trans[n], Callbacks->CallbackData); } if (Transfer) { Transfer->Empty(); } } else { Trans[n]->Oversize = Status = true; } if (Items) { Items->Value++; } } else { LgiTrace("%s:%i - No stream.\n", _FL); } } } else { LgiTrace("%s:%i - Arg check failed, len=%p, sock=%p.\n", _FL, Trans.Length(), Socket.Get()); } return Status; } bool MailPop3::GetSizes(LArray &Sizes) { if (!Socket) return false; strcpy_s(Buffer, sizeof(Buffer), (char*)"LIST\r\n"); VERIFY_RET_VAL(Write(0, true)); auto s = ReadMultiLineReply(); if (!s) return false; for (auto ln: s.SplitDelimit("\r\n")) { auto p = ln.SplitDelimit(); if (p.Length() > 1) Sizes.Add((int)p.Last().Int()); } return Sizes.Length() > 0; } int MailPop3::Sizeof(int Message) { int Size = 0; if (Socket) { sprintf_s(Buffer, sizeof(Buffer), "LIST %i\r\n", Message + 1); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply()); char *s = strchr(Buffer, ' '); if (s) { s = strchr(s+1, ' '); if (s) { Size = atoi(s); } } } return Size; } bool MailPop3::Delete(int Message) { if (Socket) { sprintf_s(Buffer, sizeof(Buffer), "DELE %i\r\n", Message + 1); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply()); return true; } return false; } bool MailPop3::GetUid(int Index, char *Id, int IdLen) { if (Socket && Id) { sprintf_s(Buffer, sizeof(Buffer), "UIDL %i\r\n", Index + 1); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply()); char *Space = strchr(Buffer, ' '); if (Space) { Space = strchr(Space+1, ' '); if (Space) { for (char *s = Space+1; *s; s++) { if (*s == '\r' || *s == '\n') { *s = 0; break; } } strcpy_s(Id, IdLen, Space+1); return true; } } } return false; } bool MailPop3::GetUidList(LString::Array &Id) { if (!Socket) return false; sprintf_s(Buffer, sizeof(Buffer), "UIDL\r\n"); VERIFY_RET_VAL(Write(0, true)); auto Str = ReadMultiLineReply(); if (!Str) return false; auto lines = Str.SplitDelimit("\r\n"); for (auto s: lines) { if (s(0) != '.') { char *Space = strchr(s, ' '); if (Space++) Id.New() = Space; } } return true; } LString MailPop3::GetHeaders(int Message) { if (!Socket) return NULL; sprintf_s(Buffer, sizeof(Buffer), "TOP %i 0\r\n", Message + 1); if (!Write(NULL, true)) return NULL; return ReadMultiLineReply(); } LString MailPop3::ReadMultiLineReply() { if (!Socket) { LAssert(!"No socket."); return false; } LString a; do { auto s = Socket->Read(); if (!s) break; a += s; if (!a || a[0] != '+') return NULL; } while (!MailIsEnd(a)); // Strip off the first line... auto FirstNewLen = a.Find("\n"); return FirstNewLen >= 0 ? a(FirstNewLen, -1) : NULL; } bool MailPop3::Close() { if (Socket) { // logout VERIFY_RET_VAL(Write("QUIT\r\n", true)); // 2 sec timeout, we don't really care about the server's response Socket->SetTimeout(2000); ReadReply(); if (SocketLock.Lock(_FL)) { Socket.Reset(0); SocketLock.Unlock(); } Messages = 0; return true; } return false; } diff --git a/src/common/Net/Mime.cpp b/src/common/Net/Mime.cpp --- a/src/common/Net/Mime.cpp +++ b/src/common/Net/Mime.cpp @@ -1,1693 +1,1692 @@ #include -#include "lgi/common/LgiNetInc.h" #include "lgi/common/Lgi.h" #include "lgi/common/Mime.h" #include "lgi/common/Base64.h" #define DEBUG_MIME 0 #if DEBUG_MIME #define LOG(...) printf(__VA_ARGS__) #else #define LOG(...) #endif static const char *MimeEol = "\r\n"; static const char *MimeWs = " \t\r\n"; static const char *MimeStr = "\'\""; static const char *MimeQuotedPrintable = "quoted-printable"; static const char *MimeBase64 = "base64"; #define MimeMagic ( ('M'<<24) | ('I'<<24) | ('M'<<24) | ('E'<<24) ) #define SkipWs(s) while (*s && strchr(MimeWs, *s)) s++ #define SkipNonWs(s) while (*s && !strchr(MimeWs, *s)) s++ const char *LMime::DefaultCharset = "text/plain"; template int CastInt(T in) { int out = (int)in; LAssert(out == in); return out; } int AltScore(char *Mt) { int Score = 0; if (Mt) { if (stristr(Mt, "/html")) Score = 1; else if (stristr(Mt, "/related")) Score = 2; } // printf("Score '%s' = %i\n", Mt, Score); DeleteArray(Mt); return Score; } int AltSortCmp(LMime **a, LMime **b) { int a_score = AltScore((*a)->GetMimeType()); int b_score = AltScore((*b)->GetMimeType()); return a_score - b_score; } /////////////////////////////////////////////////////////////////////// enum MimeBoundary { MimeData = 0, MimeNextSeg = 1, MimeEndSeg = 2 }; MimeBoundary IsMimeBoundary(char *Boundary, char *Line) { if (!Boundary || !Line) return MimeData; auto BoundaryLen = strlen(Boundary); if (Line[0] != '-' || Line[1] != '-') return MimeData; if (strncmp(Line + 2, Boundary, BoundaryLen) == 0) { printf("matched prefix: %s\n", Line); // MIME segment boundary Line += 2 + BoundaryLen; if (Line[0] == '-' && Line[1] == '-') { return MimeEndSeg; } else { return MimeNextSeg; } } else { printf("no match: line='%s' doesn't match boundry='%s'\n", Line + 2, Boundary); } return MimeData; } void CreateMimeBoundary(char *Buf, int BufLen) { if (Buf) { static int Count = 1; sprintf_s(Buf, BufLen, "--%x-%x-%x--", (int)LCurrentTime(), (int)(uint64)LGetCurrentThread(), Count++); } } /////////////////////////////////////////////////////////////////////// class LCoderStream : public LStream { protected: LStreamI *Out; public: LCoderStream(LStreamI *o) { Out = o; } }; class LMimeTextEncode : public LCoderStream { // This code needs to make sure it writes an end-of-line at // the end, otherwise a following MIME boundary could be missed. bool LastEol; public: LMimeTextEncode(LStreamI *o) : LCoderStream(o) { LastEol = false; } ~LMimeTextEncode() { if (!LastEol) { #ifdef _DEBUG ssize_t w = #endif Out->Write(MimeEol, 2); LAssert(w == 2); } } ssize_t Write(const void *p, ssize_t size, int f = 0) { // Make sure any new lines are \r\n char *s = (char*)p, *e = s + size; ssize_t wr = 0; while (s < e) { char *c = s; while (c < e && *c != '\r' && *c != '\n') c++; if (c > s) { ptrdiff_t bytes = c - s; ssize_t w = Out->Write(s, (int)bytes); if (w != bytes) return wr; wr += w; LastEol = false; } while (c < e && (*c == '\r' || *c == '\n')) { if (*c == '\n') { ssize_t w = Out->Write(MimeEol, 2); if (w != 2) return wr; LastEol = true; } wr++; c++; } s = c; } return wr; } }; class LMimeQuotedPrintableEncode : public LCoderStream { // 'd' is our current position in 'Buf' char *d; // A buffer for a line of quoted printable text char Buf[128]; public: LMimeQuotedPrintableEncode(LStreamI *o) : LCoderStream(o) { Buf[0] = 0; d = Buf; } ~LMimeQuotedPrintableEncode() { if (d > Buf) { // Write partial line *d++ = '\r'; *d++ = '\n'; ptrdiff_t Len = d - Buf; Out->Write(Buf, CastInt(Len)); } } ssize_t Write(const void *p, ssize_t size, int f = 0) { char *s = (char*)p; char *e = s + size; while (s < e) { if (*s == '\n' || !*s) { *d++ = '\r'; *d++ = '\n'; ptrdiff_t Len = d - Buf; if (Out->Write(Buf, CastInt(Len)) < Len) { LAssert(!"write error"); break; } if (!*s) { break; } d = Buf; s++; } else if (*s & 0x80 || *s == '.' || *s == '=') { int Ch = sprintf_s(d, sizeof(Buf)-(d-Buf), "=%2.2X", (uchar)*s); if (Ch < 0) { LAssert(!"printf error"); break; } d += Ch; s++; } else if (*s != '\r') { *d++ = *s++; } else { // Consume any '\r' without outputting them s++; } if (d-Buf > 73) { // time for a new line. *d++ = '='; *d++ = '\r'; *d++ = '\n'; ptrdiff_t Len = d-Buf; if (Out->Write(Buf, CastInt(Len)) < Len) { LAssert(!"write error"); break; } d = Buf; } } return CastInt(s - (const char*)p); } }; class LMimeQuotedPrintableDecode : public LCoderStream { uint8_t ConvHexToBin(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c + 10 - 'a'; if (c >= 'A' && c <= 'F') return c + 10 - 'A'; return 0; } public: LMimeQuotedPrintableDecode(LStreamI *o) : LCoderStream(o) {} ssize_t Write(const void *p, ssize_t size, int f = 0) { ssize_t Written = 0; char Line[1024]; char *o = Line; const char *s = (const char*) p; const char *e = s + size; #define NEXT(ptr) if (++ptr >= e) break for (const char *s = (const char*)p; s < e; ) { if (*s == '=') { NEXT(s); // skip '=' if (*s == '\r' || *s == '\n') { if (*s == '\r') NEXT(s); if (*s == '\n') NEXT(s); } else if (*s) { uint8_t hi = ConvHexToBin(*s++); if (s >= e) break; uint8_t low = ConvHexToBin(*s++); if (s >= e) break; *o++ = (hi << 4) | (low & 0xF); } else break; } else { *o++ = *s++; } if (o - Line > 1000) { auto w = Out->Write(Line, o - Line); if (w > 0) { Written += w; o = Line; } else break; // Error } } if (o > Line) { auto w = Out->Write(Line, o - Line); if (w > 0) Written += w; } return Written; } }; #define BASE64_LINE_SZ 76 #define BASE64_READ_SZ (BASE64_LINE_SZ*3/4) class LMimeBase64Encode : public LCoderStream { LMemQueue Buf; public: LMimeBase64Encode(LStreamI *o) : LCoderStream(o) {} ~LMimeBase64Encode() { uchar b[100]; int64 Len = Buf.GetSize(); LAssert(Len < sizeof(b)); ssize_t r = Buf.Read(b, CastInt(Len)); if (r > 0) { char t[256]; ssize_t w = ConvertBinaryToBase64(t, sizeof(t), b, r); Out->Write(t, w); Out->Write(MimeEol, 2); } } ssize_t Write(const void *p, ssize_t size, int f = 0) { Buf.Write((uchar*)p, size); int64 Sz; while ((Sz = Buf.GetSize()) >= BASE64_READ_SZ) { uchar b[100]; ssize_t r = Buf.Read(b, BASE64_READ_SZ); if (r) { char t[256]; ssize_t w = ConvertBinaryToBase64(t, sizeof(t), b, r); if (w > 0) { Out->Write(t, w); Out->Write(MimeEol, 2); } else LAssert(0); } else return 0; } return size; } }; class LMimeBase64Decode : public LCoderStream { LStringPipe Buf; uint8_t Lut[256]; public: LMimeBase64Decode(LStreamI *o) : LCoderStream(o) { ZeroObj(Lut); memset(Lut+(int)'a', 1, 'z'-'a'+1); memset(Lut+(int)'A', 1, 'Z'-'A'+1); memset(Lut+(int)'0', 1, '9'-'0'+1); Lut[(int)'+'] = 1; Lut[(int)'/'] = 1; Lut[(int)'='] = 1; } ssize_t Write(const void *p, ssize_t size, int f = 0) { ssize_t Status = 0; // Push non whitespace into the memory buf char *s = (char*)p; char *e = s + size; while (s < e) { while (*s && s < e && !Lut[(int)*s]) s++; char *Start = s; while (*s && s < e && Lut[(int)*s]) s++; if (s-Start > 0) Buf.Push(Start, CastInt(s-Start)); else break; } // While there is at least one run of base64 (4 bytes) convert it to text // and write it to the output stream int Size; while ((Size = CastInt(Buf.GetSize())) > 3) { Size &= ~3; char t[256]; ssize_t r = MIN(sizeof(t), Size); if ((r = Buf.LMemQueue::Read((uchar*)t, r)) > 0) { uchar b[256]; ssize_t w = ConvertBase64ToBinary(b, sizeof(b), t, r); Out->Write(b, w); Status += w; } } return Status; } }; /////////////////////////////////////////////////////////////////////// LMimeBuf::LMimeBuf(LStreamI *src, LStreamEnd *end) : LStringPipe(BlockSz) { Src = src; End = end; if (Src) Src->SetPos(0); } // Read some data from 'src' // \returns true if some data was received. bool LMimeBuf::ReadSrc() { if (!Src) return false; auto b = GetBuffer(); if (!b) return false; auto rd = Src->Read(b.ptr, b.len); if (rd <= 0) return false; b.Commit(rd); return true; } ssize_t LMimeBuf::Pop(LArray &Out) { ssize_t Ret = 0; while (!(Ret = LStringPipe::Pop(Out))) { if (Src) { char Buf[2048]; ssize_t r = Src ? Src->Read(Buf, sizeof(Buf)) : 0; if (r) { if (End) { ssize_t e = End->IsEnd(Buf, r); if (e >= 0) { // End of stream ssize_t s = e - Total; Push(Buf, s); Total += s; Src = 0; // no more data anyway } else { // Not the end Push(Buf, r); Total += r; } } else { Push(Buf, r); Total += r; } } else { Src = NULL; // Source data is finished } } else { // Is there any unterminated space in the string pipe? int64 Sz = LStringPipe::GetSize(); if (Sz > 0) { if ((int64)Out.Length() < Sz) Out.Length(Sz); Ret = LStringPipe::Read(Out.AddressOf(), Sz); } break; } } return Ret; } ssize_t LMimeBuf::Pop(char *Str, ssize_t BufSize) { ssize_t Ret = 0; while (!(Ret = LStringPipe::Pop(Str, BufSize))) { if (Src) { char Buf[1024]; ssize_t r = Src ? Src->Read(Buf, sizeof(Buf)) : 0; if (r) { if (End) { ssize_t e = End->IsEnd(Buf, r); if (e >= 0) { // End of stream ssize_t s = e - Total; Push(Buf, s); Total += s; Src = 0; // no more data anyway } else { // Not the end Push(Buf, r); Total += r; } } else { Push(Buf, r); Total += r; } } else { Src = NULL; // Source data is finished } } else { // Is there any unterminated space in the string pipe? int64 Sz = LStringPipe::GetSize(); if (Sz > 0) { Ret = LStringPipe::Read(Str, BufSize); } break; } } return Ret; } /////////////////////////////////////////////////////////////////////// // Mime Object LMime::LMime(const char *tmp) { Parent = 0; TmpPath = NewStr(tmp); DataPos = 0; DataSize = 0; DataLock = 0; DataStore = 0; OwnDataStore = 0; Text.Decode.Mime = this; Text.Encode.Mime = this; Binary.Read.Mime = this; Binary.Write.Mime = this; } LMime::~LMime() { Remove(); Empty(); DeleteArray(TmpPath); } char *LMime::GetFileName() { char *n = GetSub("Content-Type", "Name"); if (!n) n = GetSub("Content-Disposition", "Filename"); if (!n) { n = Get("Content-Location"); if (n) { char *trim = TrimStr(n, "\'\""); if (trim) { DeleteArray(n); n = trim; } } } return n; } LMime *LMime::NewChild() { LMime *n = new LMime(GetTmpPath()); if (n) Insert(n); return n; } bool LMime::Insert(LMime *m, int Pos) { LAssert(m != NULL); if (!m) return false; if (m->Parent) { LAssert(m->Parent->Children.HasItem(m)); m->Parent->Children.Delete(m, true); } m->Parent = this; LAssert(!Children.HasItem(m)); if (Pos >= 0) Children.AddAt(Pos, m); else Children.Add(m); return true; } void LMime::Remove() { if (Parent) { LAssert(Parent->Children.HasItem(this)); Parent->Children.Delete(this, true); Parent = 0; } } LMime *LMime::operator[](uint32_t i) { if (i >= Children.Length()) return 0; return Children[i]; } char *LMime::GetTmpPath() { for (LMime *m = this; m; m = m->Parent) { if (m->TmpPath) return m->TmpPath; } return 0; } bool LMime::SetHeaders(const char *h) { Headers = h; return Headers != 0; } bool LMime::Lock() { bool Lock = true; if (DataLock) { Lock = DataLock->Lock(_FL); } return Lock; } void LMime::Unlock() { if (DataLock) { DataLock->Unlock(); } } bool LMime::CreateTempData() { bool Status = false; DataPos = 0; DataSize = 0; OwnDataStore = true; if ((DataStore = new LTempStream(GetTmpPath(), 4 << 20))) { Status = true; } return Status; } LStreamI *LMime::GetData(bool Detach) { LStreamI *Ds = DataStore; if (Ds) { Ds->SetPos(DataPos); if (Detach) { LOG("%s:%i - Detaching %p from %p\n", _FL, DataStore, this); DataStore = 0; } } return Ds; } bool LMime::SetData(bool OwnStream, LStreamI *d, int Pos, int Size, LMutex *l) { if (DataStore && Lock()) { DeleteObj(DataStore); Unlock(); } if (d) { OwnDataStore = OwnStream; DataPos = Pos; DataSize = Size >= 0 ? Size : (int)d->GetSize(); DataLock = l; DataStore = d; } return true; } bool LMime::SetData(char *Str, int Len) { if (DataStore && Lock()) { DeleteObj(DataStore); Unlock(); } if (Str) { if (Len < 0) { Len = (int)strlen(Str); } DataLock = 0; DataPos = 0; DataSize = Len; DataStore = new LTempStream(GetTmpPath(), 4 << 20); if (DataStore) { DataStore->Write(Str, Len); } } return true; } void LMime::Empty() { if (OwnDataStore) { if (Lock()) { DeleteObj(DataStore); Unlock(); } } while (Children.Length()) delete Children[0]; Headers.Empty(); DataPos = 0; DataSize = 0; DataLock = 0; DataStore = 0; OwnDataStore = 0; } char *LMime::NewValue(char *&s, bool Alloc) { char *Status = 0; int Inc = 0; char *End; if (strchr(MimeStr, *s)) { // Delimited string char Delim = *s++; End = strchr(s, Delim); Inc = 1; } else { // Raw string End = s; while (*End && *End != ';' && *End != '\n' && *End != '\r') End++; while (strchr(MimeWs, End[-1])) End--; } if (End) { if (Alloc) { Status = NewStr(s, End-s); } s = End + Inc; } SkipWs(s); return Status; } char *LMime::StartOfField(char *s, const char *Field) { if (s && Field) { size_t FieldLen = strlen(Field); while (s && *s) { if (strchr(MimeWs, *s)) { s = strchr(s, '\n'); if (s) s++; } else { char *f = s; while (*s && *s != ':' && !strchr(MimeWs, *s)) s++; int fLen = CastInt(s - f); if (*s++ == ':' && fLen == FieldLen && _strnicmp(f, Field, FieldLen) == 0) { return f; break; } else { s = strchr(s, '\n'); if (s) s++; } } } } return 0; } char *LMime::NextField(char *s) { while (s) { while (*s && *s != '\n') s++; if (*s == '\n') { s++; if (!strchr(MimeWs, *s)) { break; } } else { break; } } return s; } char *LMime::Get(const char *Name, bool Short, const char *Default) { char *Status = 0; if (Name && Headers) { char *s = StartOfField(Headers, Name); if (s) { s = strchr(s, ':'); if (s) { s++; SkipWs(s); if (Short) { Status = NewValue(s); } else { char *e = NextField(s); while (strchr(MimeWs, e[-1])) e--; Status = NewStr(s, e-s); } } } if (!Status && Default) { Status = NewStr(Default); } } return Status; } bool LMime::Set(const char *Name, const char *Value) { if (!Name) return false; LStringPipe p; char *h = Headers; if (h) { char *f = StartOfField(h, Name); if (f) { // 'Name' exists, push out pre 'Name' header text p.Push(h, CastInt(f - Headers)); h = NextField(f); } else { if (!Value) { // Nothing to do here... return true; } // 'Name' doesn't exist, push out all the headers p.Push(Headers); h = 0; } } if (Value) { // Push new field int Vlen = CastInt(strlen(Value)); while (Vlen > 0 && strchr(MimeWs, Value[Vlen-1])) Vlen--; p.Push(Name); p.Push(": "); p.Push(Value, Vlen); p.Push(MimeEol); } // else we're deleting the feild if (h) { // Push out any header text post the 'Name' field. p.Push(h); } Headers = p.NewLStr(); return Headers != NULL; } char *LMime::GetSub(const char *Field, const char *Sub) { if (!Field || !Sub) return NULL; auto v = Get(Field, false); if (!v) return NULL; auto SubLen = strlen(Sub); char *Status = NULL; // Move past the field value into the sub fields char *s = v; SkipWs(s); while (*s && *s != ';' && !strchr(MimeWs, *s)) s++; SkipWs(s); while (s && *s++ == ';') { // Parse each name=value pair SkipWs(s); auto Name = s; while (*s && *s != '=' && !strchr(MimeWs, *s)) s++; auto NameLen = s - Name; SkipWs(s); // printf("found field '%.*s'\n", (int)NameLen, Name); if (*s++ == '=') { bool Found = SubLen == NameLen && _strnicmp(Name, Sub, NameLen) == 0; SkipWs(s); Status = NewValue(s, Found); if (Found) break; } else break; } DeleteArray(v); return Status; } bool LMime::SetSub(const char *Field, const char *Sub, const char *Value, const char *DefaultValue) { if (Field && Sub) { char Buf[256]; char *s = StartOfField(Headers, Field); if (s) { // Header already exists s = strchr(s, ':'); if (s++) { SkipWs(s); LStringPipe p; // Push the field data char *e = s; while (*e && !strchr("; \t\r\n", *e)) e++; p.Push(s, CastInt(e-s)); SkipWs(e); // Loop through the subfields and push all those that are not 'Sub' s = e; while (*s++ == ';') { SkipWs(s); char *e = s; while (*e && *e != '=' && !strchr(MimeWs, *e)) e++; char *Name = NewStr(s, e-s); if (Name) { s = e; SkipWs(s); if (*s++ == '=') { char *v = NewValue(s); if (_stricmp(Name, Sub) != 0) { sprintf_s(Buf, sizeof(Buf), ";\r\n\t%s=\"%s\"", Name, v); p.Push(Buf); } DeleteArray(v); } else break; } else break; } if (Value) { // Push the new sub field sprintf_s(Buf, sizeof(Buf), ";\r\n\t%s=\"%s\"", Sub, Value); p.Push(Buf); } char *Data = p.NewStr(); if (Data) { Set(Field, Data); DeleteArray(Data); } } } else if (DefaultValue) { // Header doesn't exist at all if (Value) { // Set sprintf_s(Buf, sizeof(Buf), "%s;\r\n\t%s=\"%s\"", DefaultValue, Sub, Value); return Set(Field, Buf); } else { // Remove return Set(Field, DefaultValue); } } } return false; } ///////////////////////////////////////////////////////////////////////// // Mime Text Conversion // Rfc822 Text -> Object ssize_t LMime::LMimeText::LMimeDecode::Pull(LStreamI *Source, LStreamEnd *End) { LMimeBuf Buf(Source, End); // Stream -> Lines return Parse(&Buf); } class ParentState { public: char *Boundary = NULL; MimeBoundary Type; ParentState() { Type = MimeData; } }; ssize_t LMime::LMimeText::LMimeDecode::Parse(LMimeBuf *Source, ParentState *State) { ssize_t Status = 0; if (!Mime || !Source) { LOG("%s:%i - Arg error %p %p.\n", _FL, Mime, Source); return Status; } Mime->Empty(); if (!Mime->CreateTempData()) { LOG("CreateTempData failed.\n"); LAssert(!"CreateTempData failed."); return Status; } LAssert(Mime->DataStore != NULL); // Read the headers.. if (Buffer.Length() == 0) Buffer.Length(1 << 10); LOG("%s:%i - Reading headers...\n", _FL); ssize_t r; while ((r = Source->Find("\r\n\r\n")) < 0) { if (!Source->ReadSrc()) break; } if (r < 0) // No break between headers and body found. return Status; // Not an error Mime->Headers = Source->ReadStr(r + 4); LOG("%s:%i - Mime->Headers=%i\n", _FL, Mime->Headers?(int)Mime->Headers.Length():-1); // Get various bits out of the header LAutoString Encoding(Mime->GetEncoding()); LAutoString Boundary(Mime->GetBoundary()); LAutoString MimeType(Mime->GetMimeType()); LOG("%s:%i - Encoding=%s, MimeType=%s, Boundary=%s\n", _FL, Encoding.Get(), MimeType.Get(), Boundary.Get()); LStream *Decoder = 0; if (Encoding) { if (_stricmp(Encoding, MimeQuotedPrintable) == 0) { Decoder = new LMimeQuotedPrintableDecode(Mime->DataStore); LOG("%s:%i - Using LMimeQuotedPrintableDecode\n", _FL); } else if (_stricmp(Encoding, MimeBase64) == 0) { Decoder = new LMimeBase64Decode(Mime->DataStore); LOG("%s:%i - Using LMimeBase64Decode\n", _FL); } else { LOG("%s:%i - Unknown encoding '%s'\n", _FL, Encoding); } } Encoding.Reset(); // Read in the rest of the MIME segment bool Done = false; // int64 StartPos = Mime->DataStore->GetPos(); while (!Done) { // Process existing lines ssize_t Len; ssize_t Written = 0; Status = true; while ((Len = Source->Pop(Buffer)) > 0) { // Check for boundary MimeBoundary Type = MimeData; auto b = Buffer.AddressOf(); if (Boundary) { bool CouldBe = Buffer.Length() > 2 && b[0] == '-' && b[1] == '-'; Type = IsMimeBoundary(Boundary, b); if (Type) { LOG("%s:%i - IsMimeBoundary=%i\n", _FL, Type); } else if (CouldBe) { LOG("%s:%i - CouldBe '%s'\n", _FL, b); } } if (State) { State->Type = IsMimeBoundary(State->Boundary, b); if (State->Type) { LOG("%s:%i - IsMimeBoundary=%i\n", _FL, State->Type); Status = Done = true; break; } } DoSegment: if (Type == MimeNextSeg) { ParentState MyState; MyState.Boundary = Boundary; LMime *Seg = new LMime(Mime->GetTmpPath()); if (Seg && Seg->Text.Decode.Parse(Source, &MyState)) { LOG("%s:%i - Inserting child seg.\n", _FL); Mime->Insert(Seg); if (MyState.Type) { Type = MyState.Type; goto DoSegment; } } else { LOG("%s:%i - Text.Decode.Parse failed.\n", _FL); break; } } else if (Type == MimeEndSeg) { Done = true; LOG("%s:%i - MimeEndSeg.\n", _FL); break; } else { // Process data if (Decoder) { Written += Decoder->Write(Buffer.AddressOf(), Len); } else { ssize_t w = Mime->DataStore->Write(Buffer.AddressOf(), Len); if (w > 0) { Written += w; } else { LOG("%s:%i - w 0\n", _FL); Done = true; Status = false; break; } } } } Mime->DataSize = Written; if (Len == 0) { LOG("%s:%i - Len 0\n", _FL); Done = true; } } LOG("%s:%i - Finished\n", _FL); return Status; } void LMime::LMimeText::LMimeDecode::Empty() { } // Object -> Rfc822 Text ssize_t LMime::LMimeText::LMimeEncode::Push(LStreamI *Dest, LStreamEnd *End) { int Status = 0; if (Mime) { char Buf[1024]; int Ch; // Check boundary char *Boundary = Mime->GetBoundary(); if (Mime->Children.Length()) { // Boundary required if (!Boundary) { // Create one char b[256]; CreateMimeBoundary(b, sizeof(b)); Mime->SetBoundary(b); Boundary = Mime->GetBoundary(); } } else if (Boundary) { // Remove boundary Mime->SetBoundary(0); DeleteArray(Boundary); } // Check encoding char *Encoding = Mime->GetEncoding(); if (!Encoding) { // Detect an appropriate encoding int MaxLine = 0; bool Has8Bit = false; bool HasBin = false; if (Mime->DataStore && Mime->Lock()) { Mime->DataStore->SetPos(Mime->DataPos); int x = 0; for (ssize_t i=0; iDataSize; ) { ssize_t m = MIN(Mime->DataSize - i, sizeof(Buf)); ssize_t r = Mime->DataStore->Read(Buf, m); if (r > 0) { for (int n=0; nUnlock(); } if (HasBin) { Encoding = NewStr(MimeBase64); } else if (Has8Bit || MaxLine > 70) { Encoding = NewStr(MimeQuotedPrintable); } if (Encoding) { Mime->SetEncoding(Encoding); } } // Write the headers auto h = Mime->Headers.SplitDelimit(MimeEol); for (unsigned i=0; iWrite(h[i], CastInt(strlen(h[i]))); Dest->Write(MimeEol, 2); } Dest->Write(MimeEol, 2); // Write data LStream *Encoder = 0; if (Encoding) { if (_stricmp(Encoding, MimeQuotedPrintable) == 0) { Encoder = new LMimeQuotedPrintableEncode(Dest); } else if (_stricmp(Encoding, MimeBase64) == 0) { Encoder = new LMimeBase64Encode(Dest); } } if (!Encoder) { Encoder = new LMimeTextEncode(Dest); } if (Mime->DataStore) { if (Mime->Lock()) { Mime->DataStore->SetPos(Mime->DataPos); Status = Mime->DataSize == 0; // Nothing is a valid segment?? for (int i=0; iDataSize; ) { ssize_t m = MIN(Mime->DataSize-i, sizeof(Buf)); ssize_t r = Mime->DataStore->Read(Buf, m); if (r > 0) { Encoder->Write(Buf, r); Status = true; } else break; } Mime->Unlock(); } } else { Status = true; } DeleteObj(Encoder); // Write children if (Mime->Children.Length() && Boundary) { LAutoString Mt(Mime->GetMimeType()); if (Mt && !_stricmp(Mt, "multipart/alternative")) { // Sort the children to order richer content at the bottom... Mime->Children.Sort(AltSortCmp); } for (unsigned i=0; iChildren.Length(); i++) { Ch = sprintf_s(Buf, sizeof(Buf), "--%s\r\n", Boundary); Dest->Write(Buf, Ch); if (!Mime->Children[i]->Text.Encode.Push(Dest, End)) { break; } Status = 1; } Ch = sprintf_s(Buf, sizeof(Buf), "--%s--\r\n", Boundary); Dest->Write(Buf, Ch); } // Clean up DeleteArray(Encoding); DeleteArray(Boundary); } return Status; } void LMime::LMimeText::LMimeEncode::Empty() { } ///////////////////////////////////////////////////////////////////////// // Mime Binary Serialization // Source -> Object ssize_t LMime::LMimeBinary::LMimeRead::Pull(LStreamI *Source, LStreamEnd *End) { if (Source) { int32 Header[4]; Mime->Empty(); // Read header block (Magic, HeaderSize, DataSize, # of Children) // and check magic if (Source->Read(Header, sizeof(Header)) == sizeof(Header) && Header[0] == MimeMagic) { // Read header data Mime->Headers.Length(Header[1]+1); if (Mime->Headers && Source->Read(Mime->Headers, Header[1]) == Header[1]) { // NUL terminate Mime->Headers.Get()[Mime->Headers.Length()] = 0; // Skip body data if (Source->SetPos(Source->GetPos() + Header[2]) > 0) { // Read the children in for (int i=0; iGetTmpPath()); if (c && c->Binary.Read.Pull(Source, End)) { Mime->Insert(c); } else break; } } } return 1; // success } } return 0; // failure } void LMime::LMimeBinary::LMimeRead::Empty() { } // Object -> Dest int64 LMime::LMimeBinary::LMimeWrite::GetSize() { int64 Size = 0; if (Mime) { Size = (sizeof(int32) * 4) + // Header magic + block sizes (Mime->Headers ? Mime->Headers.Length() : 0) + // Headers (Mime->DataStore ? Mime->DataSize : 0); // Data // Children for (unsigned i=0; iChildren.Length(); i++) { Size += Mime->Children[i]->Binary.Write.GetSize(); } } return Size; } ssize_t LMime::LMimeBinary::LMimeWrite::Push(LStreamI *Dest, LStreamEnd *End) { if (Dest && Mime) { int32 Header[4] = { MimeMagic, Mime->Headers ? (int32)Mime->Headers.Length() : 0, Mime->DataStore ? (int32)Mime->DataSize : 0, (int32) Mime->Children.Length() }; if (Dest->Write(Header, sizeof(Header)) == sizeof(Header)) { if (Mime->Headers) { Dest->Write(Mime->Headers, Header[1]); } if (Mime->DataStore) { char Buf[1024]; ssize_t Written = 0; ssize_t Read = 0; ssize_t r; while ((r = Mime->DataStore->Read(Buf, MIN(sizeof(Buf), Header[2]-Read) )) > 0) { ssize_t w; if ((w = Dest->Write(Buf, r)) <= 0) { // Write error break; } Written += w; Read += r; } // Check we've written out all the data LAssert(Written < Header[2]); if (Written < Header[2]) { return 0; } } for (unsigned i=0; iChildren.Length(); i++) { Mime->Children[i]->Binary.Write.Push(Dest, End); } return 1; } } return 0; } void LMime::LMimeBinary::LMimeWrite::Empty() { } diff --git a/src/common/Text/Html.cpp b/src/common/Text/Html.cpp --- a/src/common/Text/Html.cpp +++ b/src/common/Text/Html.cpp @@ -1,9475 +1,9476 @@ #include #include #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Html.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/Variant.h" #include "lgi/common/FindReplaceDlg.h" #include "lgi/common/Unicode.h" #include "lgi/common/Emoji.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/Button.h" #include "lgi/common/Edit.h" #include "lgi/common/Combo.h" #include "lgi/common/GdcTools.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Palette.h" #include "lgi/common/Path.h" #include "lgi/common/CssTools.h" #include "lgi/common/LgiRes.h" #include "lgi/common/Net.h" #include "lgi/common/Base64.h" #include "lgi/common/Menu.h" #include "lgi/common/FindReplaceDlg.h" #include "lgi/common/Homoglyphs.h" #include "lgi/common/Charset.h" +#include "lgi/common/Uri.h" #include "HtmlPriv.h" #define DEBUG_TABLE_LAYOUT 1 #define DEBUG_DRAW_TD 0 #define DEBUG_RESTYLE 0 #define DEBUG_TAG_BY_POS 0 #define DEBUG_SELECTION 0 #define DEBUG_TEXT_AREA 0 #define ENABLE_IMAGE_RESIZING 1 #define DOCUMENT_LOAD_IMAGES 1 #define MAX_RECURSION_DEPTH 300 #define ALLOW_TABLE_GROWTH 1 #define LGI_HTML_MAXPAINT_TIME 350 // ms #define FLOAT_TOLERANCE 0.001 #define CRASH_TRACE 0 #ifdef MAC #define HTML_USE_DOUBLE_BUFFER 0 #else #define HTML_USE_DOUBLE_BUFFER 1 #endif #define GT_TRANSPARENT 0x00000000 #ifndef IDC_HAND #define IDC_HAND MAKEINTRESOURCE(32649) #endif #undef CellSpacing #define DefaultCellSpacing 0 #define DefaultCellPadding 1 #ifdef MAC #define MinimumPointSize 9 #define MinimumBodyFontSize 12 #else #define MinimumPointSize 8 #define MinimumBodyFontSize 11 #endif // #define DefaultFont "font-family: Times; font-size: 16pt;" #define DefaultBodyMargin "5px" #define DefaultImgSize 16 #define DefaultMissingCellColour GT_TRANSPARENT // Rgb32(0xf0,0xf0,0xf0) #define ShowNbsp 0 #define FontPxHeight(fnt) (fnt->GetHeight() - (int)(fnt->Leading() + 0.5)) #if 0 // def _DEBUG #define DefaultTableBorder Rgb32(0xf8, 0xf8, 0xf8) #else #define DefaultTableBorder GT_TRANSPARENT #endif #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT #define DEBUG_LOG(...) if (Table->Debug) LgiTrace(__VA_ARGS__) #else #define DEBUG_LOG(...) #endif #define IsTableCell(id) ( ((id) == TAG_TD) || ((id) == TAG_TH) ) #define IsTableTag() (TagId == TAG_TABLE || TagId == TAG_TR || TagId == TAG_TD || TagId == TAG_TH) #define GetCssLen(a, b) a().Type == LCss::LenInherit ? b() : a() static char WordDelim[] = ".,<>/?[]{}()*&^%$#@!+|\'\""; static char16 WhiteW[] = {' ', '\t', '\r', '\n', 0}; #if 0 static char DefaultCss[] = { "a { color: blue; text-decoration: underline; }" "body { margin: 8px; }" "strong { font-weight: bolder; }" "pre { font-family: monospace }" "h1 { font-size: 2em; margin: .67em 0px; }" "h2 { font-size: 1.5em; margin: .75em 0px; }" "h3 { font-size: 1.17em; margin: .83em 0px; }" "h4, p," "blockquote, ul," "fieldset, form," "ol, dl, dir," "menu { margin: 1.12em 0px; }" "h5 { font-size: .83em; margin: 1.5em 0px; }" "h6 { font-size: .75em; margin: 1.67em 0px; }" "strike, del { text-decoration: line-through; }" "hr { border: 1px inset; }" "center { text-align: center; }" "h1, h2, h3, h4," "h5, h6, b," "strong { font-weight: bolder; }" }; #endif template void RemoveChars(T *str, T *remove_list) { T *i = str, *o = str, *c; while (*i) { for (c = remove_list; *c; c++) { if (*c == *i) break; } if (*c == 0) *o++ = *i; i++; } *o++ = NULL; } ////////////////////////////////////////////////////////////////////// using namespace Html1; namespace Html1 { class LHtmlPrivate { public: LHashTbl, LTag*> Loading; LHtmlStaticInst Inst; bool CursorVis; LRect CursorPos; LPoint Content; bool WordSelectMode; bool LinkDoubleClick; LAutoString OnLoadAnchor; bool DecodeEmoji; LAutoString EmojiImg; int NextCtrlId; uint64 SetScrollTime; int DeferredLoads; bool IsParsing; bool IsLoaded; bool StyleDirty; // Paint time limits... bool MaxPaintTimeout = false; int MaxPaintTime = LGI_HTML_MAXPAINT_TIME; // Find settings LAutoWString FindText; bool MatchCase; LHtmlPrivate() { IsLoaded = false; StyleDirty = false; IsParsing = false; LinkDoubleClick = true; WordSelectMode = false; NextCtrlId = 2000; SetScrollTime = 0; CursorVis = false; CursorPos.ZOff(-1, -1); DeferredLoads = 0; char EmojiPng[MAX_PATH_LEN]; #ifdef MAC LMakePath(EmojiPng, sizeof(EmojiPng), LGetExeFile(), "Contents/Resources/Emoji.png"); #else LGetSystemPath(LSP_APP_INSTALL, EmojiPng, sizeof(EmojiPng)); LMakePath(EmojiPng, sizeof(EmojiPng), EmojiPng, "resources/emoji.png"); #endif if (LFileExists(EmojiPng)) { DecodeEmoji = true; EmojiImg.Reset(NewStr(EmojiPng)); } else DecodeEmoji = false; } ~LHtmlPrivate() { } }; class InputButton : public LButton { LTag *Tag; public: InputButton(LTag *tag, int Id, const char *Label) : LButton(Id, 0, 0, -1, -1, Label) { Tag = tag; } void OnClick(const LMouse &m) { Tag->OnClick(m); } }; class LFontCache { LHtml *Owner; List Fonts; public: LFontCache(LHtml *owner) { Owner = owner; } ~LFontCache() { Fonts.DeleteObjects(); } LFont *FontAt(int i) { return Fonts.ItemAt(i); } LFont *FindMatch(LFont *m) { for (auto f: Fonts) { if (*f == *m) { return f; } } return 0; } LFont *GetFont(LCss *Style) { if (!Style) return NULL; LFont *Default = Owner->GetFont(); LCss::StringsDef Face = Style->FontFamily(); if (Face.Length() < 1 || !ValidStr(Face[0])) { Face.Empty(); const char *DefFace = Default->Face(); LAssert(ValidStr(DefFace)); Face.Add(NewStr(DefFace)); } LAssert(ValidStr(Face[0])); LCss::Len Size = Style->FontSize(); LCss::FontWeightType Weight = Style->FontWeight(); bool IsBold = Weight == LCss::FontWeightBold || Weight == LCss::FontWeightBolder || Weight > LCss::FontWeight400; bool IsItalic = Style->FontStyle() == LCss::FontStyleItalic; bool IsUnderline = Style->TextDecoration() == LCss::TextDecorUnderline; if (Size.Type == LCss::LenInherit || Size.Type == LCss::LenNormal) { Size.Type = LCss::LenPt; Size.Value = (float)Default->PointSize(); } auto Scale = Owner->GetDpiScale(); if (Size.Type == LCss::LenPx) { Size.Value *= (float) Scale.y; int RequestPx = (int) Size.Value; // Look for cached fonts of the right size... for (auto f: Fonts) { if (f->Face() && _stricmp(f->Face(), Face[0]) == 0 && f->Bold() == IsBold && f->Italic() == IsItalic && f->Underline() == IsUnderline) { int Px = FontPxHeight(f); int Diff = Px - RequestPx; if (Diff >= 0 && Diff <= 2) return f; } } } else if (Size.Type == LCss::LenPt) { double Pt = Size.Value; for (auto f: Fonts) { if (!f->Face() || Face.Length() == 0) { LAssert(0); break; } auto FntSz = f->Size(); if (f->Face() && _stricmp(f->Face(), Face[0]) == 0 && FntSz.Type == LCss::LenPt && std::abs(FntSz.Value - Pt) < FLOAT_TOLERANCE && f->Bold() == IsBold && f->Italic() == IsItalic && f->Underline() == IsUnderline) { // Return cached font return f; } } } else if (Size.Type == LCss::LenPercent) { // Most of the percentages will be resolved in the "Apply" stage // of the CSS calculations, any that appear here have no "font-size" // in their parent tree, so we just use the default font size times // the requested percent Size.Type = LCss::LenPt; Size.Value *= Default->PointSize() / 100.0f; if (Size.Value < MinimumPointSize) Size.Value = MinimumPointSize; } else if (Size.Type == LCss::LenEm) { // Most of the relative sizes will be resolved in the "Apply" stage // of the CSS calculations, any that appear here have no "font-size" // in their parent tree, so we just use the default font size times // the requested percent Size.Type = LCss::LenPt; Size.Value *= Default->PointSize(); if (Size.Value < MinimumPointSize) Size.Value = MinimumPointSize; } else if (Size.Type == LCss::SizeXXSmall || Size.Type == LCss::SizeXSmall || Size.Type == LCss::SizeSmall || Size.Type == LCss::SizeMedium || Size.Type == LCss::SizeLarge || Size.Type == LCss::SizeXLarge || Size.Type == LCss::SizeXXLarge) { int Idx = Size.Type-LCss::SizeXXSmall; LAssert(Idx >= 0 && Idx < CountOf(LCss::FontSizeTable)); Size.Type = LCss::LenPt; Size.Value = Default->PointSize() * LCss::FontSizeTable[Idx]; if (Size.Value < MinimumPointSize) Size.Value = MinimumPointSize; } else if (Size.Type == LCss::SizeSmaller) { Size.Type = LCss::LenPt; Size.Value = (float)(Default->PointSize() - 1); } else if (Size.Type == LCss::SizeLarger) { Size.Type = LCss::LenPt; Size.Value = (float)(Default->PointSize() + 1); } else LAssert(!"Not impl."); LFont *f; if ((f = new LFont)) { auto ff = ValidStr(Face[0]) ? Face[0] : Default->Face(); f->Face(ff); f->Size(Size.IsValid() ? Size : Default->Size()); f->Bold(IsBold); f->Italic(IsItalic); f->Underline(IsUnderline); // printf("Add cache font %s,%i %i,%i,%i\n", f->Face(), f->PointSize(), f->Bold(), f->Italic(), f->Underline()); if (std::abs(Size.Value) < FLOAT_TOLERANCE) ; else if (!f->Create((char*)0, 0)) { // Broken font... f->Face(Default->Face()); LFont *DefMatch = FindMatch(f); // printf("Falling back to default face for '%s:%i', DefMatch=%p\n", ff, f->PointSize(), DefMatch); if (DefMatch) { DeleteObj(f); return DefMatch; } else { if (!f->Create((char*)0, 0)) { DeleteObj(f); return Fonts[0]; } } } // Not already cached Fonts.Insert(f); if (!f->Face()) { LAssert(0); } return f; } return 0; } }; class LFlowRegion { LCss::LengthType Align = LCss::LenInherit; List Line; // These pointers aren't owned by the flow region // When the line is finish, all the tag regions // will need to be vertically aligned struct LFlowStack { int LeftAbs; int RightAbs; int TopAbs; }; LArray Stack; public: LHtml *Html; int x1, x2; // Left and right margins int y1; // Current y position int y2; // Maximum used y position int cx; // Current insertion point int my; // How much of the area above y2 was just margin LPoint MAX; // Max dimensions int Inline; int InBody; LFlowRegion(LHtml *html, bool inbody) { Html = html; x1 = x2 = y1 = y2 = cx = my = 0; Inline = 0; InBody = inbody; } LFlowRegion(LHtml *html, LRect r, bool inbody) { Html = html; MAX.x = cx = x1 = r.x1; MAX.y = y1 = y2 = r.y1; x2 = r.x2; my = 0; Inline = 0; InBody = inbody; } LFlowRegion(LFlowRegion &r) { Html = r.Html; x1 = r.x1; x2 = r.x2; y1 = r.y1; MAX.x = cx = r.cx; MAX.y = y2 = r.y2; my = r.my; Inline = r.Inline; InBody = r.InBody; } LString ToString() { LString s; s.Printf("Flow: x=%i(%i)%i y=%i,%i my=%i inline=%i", x1, cx, x2, y1, y2, my, Inline); return s; } int X() { return x2 - cx; } void X(int newx) { x2 = x1 + newx - 1; } int Width() { return x2 - x1 + 1; } LFlowRegion &operator +=(LRect r) { x1 += r.x1; cx += r.x1; x2 -= r.x2; y1 += r.y1; y2 += r.y1; return *this; } LFlowRegion &operator -=(LRect r) { x1 -= r.x1; cx -= r.x1; x2 += r.x2; y1 += r.y2; y2 += r.y2; return *this; } void AlignText(); void FinishLine(bool Margin = false); void EndBlock(); void Insert(LFlowRect *Tr, LCss::LengthType Align); LRect *LineBounds(); void Indent(LTag *Tag, LCss::Len Left, LCss::Len Top, LCss::Len Right, LCss::Len Bottom, bool IsMargin) { LFlowRegion This(*this); LFlowStack &Fs = Stack.New(); Fs.LeftAbs = Left.IsValid() ? ResolveX(Left, Tag, IsMargin) : 0; Fs.RightAbs = Right.IsValid() ? ResolveX(Right, Tag, IsMargin) : 0; Fs.TopAbs = Top.IsValid() ? ResolveY(Top, Tag, IsMargin) : 0; x1 += Fs.LeftAbs; cx += Fs.LeftAbs; x2 -= Fs.RightAbs; y1 += Fs.TopAbs; y2 += Fs.TopAbs; if (IsMargin) my += Fs.TopAbs; } void Indent(LRect &Px, bool IsMargin) { LFlowRegion This(*this); LFlowStack &Fs = Stack.New(); Fs.LeftAbs = Px.x1; Fs.RightAbs = Px.x2; Fs.TopAbs = Px.y1; x1 += Fs.LeftAbs; cx += Fs.LeftAbs; x2 -= Fs.RightAbs; y1 += Fs.TopAbs; y2 += Fs.TopAbs; if (IsMargin) my += Fs.TopAbs; } void Outdent(LRect &Px, bool IsMargin) { LFlowRegion This = *this; ssize_t len = Stack.Length(); if (len > 0) { LFlowStack &Fs = Stack[len-1]; int &BottomAbs = Px.y2; x1 -= Fs.LeftAbs; cx -= Fs.LeftAbs; x2 += Fs.RightAbs; y2 += BottomAbs; if (IsMargin) my += BottomAbs; Stack.Length(len-1); } else LAssert(!"Nothing to pop."); } void Outdent(LTag *Tag, LCss::Len Left, LCss::Len Top, LCss::Len Right, LCss::Len Bottom, bool IsMargin) { LFlowRegion This = *this; ssize_t len = Stack.Length(); if (len > 0) { LFlowStack &Fs = Stack[len-1]; int BottomAbs = Bottom.IsValid() ? ResolveY(Bottom, Tag, IsMargin) : 0; x1 -= Fs.LeftAbs; cx -= Fs.LeftAbs; x2 += Fs.RightAbs; y2 += BottomAbs; if (IsMargin) my += BottomAbs; Stack.Length(len-1); } else LAssert(!"Nothing to pop."); } int ResolveX(LCss::Len l, LTag *t, bool IsMargin) { LFont *f = t->GetFont(); switch (l.Type) { default: case LCss::LenInherit: return IsMargin ? 0 : X(); case LCss::LenPx: // return MIN((int)l.Value, X()); return (int)l.Value; case LCss::LenPt: return (int) (l.Value * LScreenDpi().x / 72.0); case LCss::LenCm: return (int) (l.Value * LScreenDpi().x / 2.54); case LCss::LenEm: { if (!f) { LAssert(!"No font?"); f = LSysFont; } return (int)(l.Value * f->GetHeight()); } case LCss::LenEx: { if (!f) { LAssert(!"No font?"); f = LSysFont; } return (int) (l.Value * f->GetHeight() / 2); // More haha, who uses 'ex' anyway? } case LCss::LenPercent: { int my_x = X(); int px = (int) (l.Value * my_x / 100.0); return px; } case LCss::LenAuto: { if (IsMargin) return 0; else return X(); break; } case LCss::SizeSmall: { return 1; // px } case LCss::SizeMedium: { return 2; // px } case LCss::SizeLarge: { return 3; // px } } return 0; } bool LimitX(int &x, LCss::Len Min, LCss::Len Max, LFont *f) { bool Limited = false; if (Min.IsValid()) { int Px = Min.ToPx(x2 - x1 + 1, f, false); if (Px > x) { x = Px; Limited = true; } } if (Max.IsValid()) { int Px = Max.ToPx(x2 - x1 + 1, f, false); if (Px < x) { x = Px; Limited = true; } } return Limited; } int ResolveY(LCss::Len l, LTag *t, bool IsMargin) { LFont *f = t->GetFont(); switch (l.Type) { case LCss::LenInherit: case LCss::LenAuto: case LCss::LenNormal: case LCss::LenPx: return (int)l.Value; case LCss::LenPt: return (int) (l.Value * LScreenDpi().y / 72.0); case LCss::LenCm: return (int) (l.Value * LScreenDpi().y / 2.54); case LCss::LenEm: { if (!f) { f = LSysFont; LAssert(!"No font"); } return (int) (l.Value * f->GetHeight()); } case LCss::LenEx: { if (!f) { f = LSysFont; LAssert(!"No font"); } return (int) (l.Value * f->GetHeight() / 2); // More haha, who uses 'ex' anyway? } case LCss::LenPercent: { // Walk up tree of tags to find an absolute size... LCss::Len Ab; for (LTag *p = ToTag(t->Parent); p; p = ToTag(p->Parent)) { auto h = p->Height(); if (h.IsValid() && !h.IsDynamic()) { Ab = h; break; } } if (!Ab.IsValid()) { LAssert(Html != NULL); Ab.Type = LCss::LenPx; Ab.Value = (float)Html->Y(); } LCss::Len m = Ab * l; return (int)m.ToPx(0, f);; } case LCss::SizeSmall: { return 1; // px } case LCss::SizeMedium: { return 2; // px } case LCss::SizeLarge: { return 3; // px } case LCss::AlignLeft: case LCss::AlignRight: case LCss::AlignCenter: case LCss::AlignJustify: case LCss::VerticalBaseline: case LCss::VerticalSub: case LCss::VerticalSuper: case LCss::VerticalTop: case LCss::VerticalTextTop: case LCss::VerticalMiddle: case LCss::VerticalBottom: case LCss::VerticalTextBottom: { // Meaningless in this context break; } default: { LAssert(!"Not supported."); break; } } return 0; } bool LimitY(int &y, LCss::Len Min, LCss::Len Max, LFont *f) { bool Limited = false; int TotalY = Html ? Html->Y() : 0; if (Min.IsValid()) { int Px = Min.ToPx(TotalY, f, false); if (Px > y) { y = Px; Limited = true; } } if (Max.IsValid()) { int Px = Max.ToPx(TotalY, f, false); if (Px < y) { y = Px; Limited = true; } } return Limited; } LRect ResolveMargin(LCss *Src, LTag *Tag) { LRect r; r.x1 = ResolveX(Src->MarginLeft(), Tag, true); r.y1 = ResolveY(Src->MarginTop(), Tag, true); r.x2 = ResolveX(Src->MarginRight(), Tag, true); r.y2 = ResolveY(Src->MarginBottom(), Tag, true); return r; } LRect ResolveBorder(LCss *Src, LTag *Tag) { LRect r; r.x1 = ResolveX(Src->BorderLeft(), Tag, true); r.y1 = ResolveY(Src->BorderTop(), Tag, true); r.x2 = ResolveX(Src->BorderRight(), Tag, true); r.y2 = ResolveY(Src->BorderBottom(), Tag, true); return r; } LRect ResolvePadding(LCss *Src, LTag *Tag) { LRect r; r.x1 = ResolveX(Src->PaddingLeft(), Tag, true); r.y1 = ResolveY(Src->PaddingTop(), Tag, true); r.x2 = ResolveX(Src->PaddingRight(), Tag, true); r.y2 = ResolveY(Src->PaddingBottom(), Tag, true); return r; } }; }; ////////////////////////////////////////////////////////////////////// static bool ParseDistance(char *s, float &d, char *units = 0) { if (!s) return false; while (*s && IsWhiteSpace(*s)) s++; if (!IsDigit(*s) && !strchr("-.", *s)) return false; d = (float)atof(s); while (*s && (IsDigit(*s) || strchr("-.", *s))) s++; while (*s && IsWhiteSpace(*s)) s++; char _units[128]; char *o = units = units ? units : _units; while (*s && (IsAlpha(*s) || *s == '%')) { *o++ = *s++; } *o++ = 0; return true; } LHtmlLength::LHtmlLength() { d = 0; PrevAbs = 0; u = LCss::LenInherit; } LHtmlLength::LHtmlLength(char *s) { Set(s); } bool LHtmlLength::IsValid() { return u != LCss::LenInherit; } bool LHtmlLength::IsDynamic() { return u == LCss::LenPercent || d == 0.0; } LHtmlLength::operator float () { return d; } LHtmlLength &LHtmlLength::operator =(float val) { d = val; u = LCss::LenPx; return *this; } LCss::LengthType LHtmlLength::GetUnits() { return u; } void LHtmlLength::Set(char *s) { if (ValidStr(s)) { char Units[256] = ""; if (ParseDistance(s, d, Units)) { if (Units[0]) { if (strchr(Units, '%')) { u = LCss::LenPercent; } else if (stristr(Units, "pt")) { u = LCss::LenPt; } else if (stristr(Units, "em")) { u = LCss::LenEm; } else if (stristr(Units, "ex")) { u = LCss::LenEx; } else { u = LCss::LenPx; } } else { u = LCss::LenPx; } } } } float LHtmlLength::Get(LFlowRegion *Flow, LFont *Font, bool Lock) { switch (u) { default: break; case LCss::LenEm: { return PrevAbs = d * (Font ? Font->GetHeight() : 14); break; } case LCss::LenEx: { return PrevAbs = (Font ? Font->GetHeight() * d : 14) / 2; break; } case LCss::LenPercent: { if (Lock || PrevAbs == 0.0) { return PrevAbs = (Flow->X() * d / 100); } else { return PrevAbs; } break; } } float FlowX = Flow ? Flow->X() : d; return PrevAbs = MIN(FlowX, d); } LHtmlLine::LHtmlLine() { LineStyle = -1; LineReset = 0x80000000; } LHtmlLine::~LHtmlLine() { } LHtmlLine &LHtmlLine::operator =(int i) { d = (float)i; return *this; } void LHtmlLine::Set(char *s) { auto t = LString(s).SplitDelimit(" \t"); LineReset = 0x80000000; LineStyle = -1; char *Style = 0; for (unsigned i=0; iColourMap.Find(c) ) { LHtmlParser::ParseColour(c, Colour); } else if (_strnicmp(c, "rgb(", 4) == 0) { char Buf[256]; strcpy_s(Buf, sizeof(Buf), c); while (!strchr(c, ')') && (c = t[++i])) { strcat(Buf, c); } LHtmlParser::ParseColour(Buf, Colour); } else if (IsDigit(*c)) { LHtmlLength::Set(c); } else if (_stricmp(c, "none") == 0) { Style = 0; } else if ( _stricmp(c, "dotted") == 0 || _stricmp(c, "dashed") == 0 || _stricmp(c, "solid") == 0 || _stricmp(c, "float") == 0 || _stricmp(c, "groove") == 0 || _stricmp(c, "ridge") == 0 || _stricmp(c, "inset") == 0 || _stricmp(c, "outse") == 0) { Style = c; } else { // ??? } } if (Style && _stricmp(Style, "dotted") == 0) { switch ((int)d) { case 2: { LineStyle = 0xcccccccc; break; } case 3: { LineStyle = 0xe38e38; LineReset = 0x800000; break; } case 4: { LineStyle = 0xf0f0f0f0; break; } case 5: { LineStyle = 0xf83e0; LineReset = 0x80000; break; } case 6: { LineStyle = 0xfc0fc0; LineReset = 0x800000; break; } case 7: { LineStyle = 0xfe03f80; LineReset = 0x8000000; break; } case 8: { LineStyle = 0xff00ff00; break; } case 9: { LineStyle = 0x3fe00; LineReset = 0x20000; break; } default: { LineStyle = 0xaaaaaaaa; break; } } } } ////////////////////////////////////////////////////////////////////// LRect LTag::GetRect(bool Client) { LRect r(Pos.x, Pos.y, Pos.x + Size.x - 1, Pos.y + Size.y - 1); if (!Client) { for (LTag *p = ToTag(Parent); p; p=ToTag(p->Parent)) { r.Offset(p->Pos.x, p->Pos.y); } } return r; } LCss::LengthType LTag::GetAlign(bool x) { for (LTag *t = this; t; t = ToTag(t->Parent)) { LCss::Len l; if (x) { if (IsTableCell(TagId) && Cell && Cell->XAlign) l.Type = Cell->XAlign; else l = t->TextAlign(); } else { l = t->VerticalAlign(); } if (l.Type != LenInherit) { return l.Type; } if (t->TagId == TAG_TABLE) break; } return LenInherit; } ////////////////////////////////////////////////////////////////////// void LFlowRegion::EndBlock() { if (cx > x1) FinishLine(); } void LFlowRegion::AlignText() { if (Align != LCss::AlignLeft) { int Used = 0; for (auto l : Line) Used += l->X(); int Total = x2 - x1 + 1; if (Used < Total) { int Offset = 0; if (Align == LCss::AlignCenter) Offset = (Total - Used) / 2; else if (Align == LCss::AlignRight) Offset = Total - Used; if (Offset) for (auto l : Line) { if (l->Tag->Display() != LCss::DispInlineBlock) l->Offset(Offset, 0); } } } } void LFlowRegion::FinishLine(bool Margin) { // AlignText(); if (y2 > y1) { my = Margin ? y2 - y1 : 0; y1 = y2; } else { int fy = Html->DefFont()->GetHeight(); my = Margin ? fy : 0; y1 += fy; } cx = x1; y2 = y1; Line.Empty(); } LRect *LFlowRegion::LineBounds() { auto It = Line.begin(); LFlowRect *Prev = *It; LFlowRect *r=Prev; if (r) { LRect b; b = *r; int Ox = r->Tag->AbsX(); int Oy = r->Tag->AbsY(); b.Offset(Ox, Oy); // int Ox = 0, Oy = 0; while ((r = *(++It) )) { LRect c = *r; Ox = r->Tag->AbsX(); Oy = r->Tag->AbsY(); c.Offset(Ox, Oy); /* Ox += r->Tag->Pos.x - Prev->Tag->Pos.x; Oy += r->Tag->Pos.y - Prev->Tag->Pos.y; c.Offset(Ox, Oy); */ b.Union(&c); Prev = r; } static LRect Rgn; Rgn = b; return &Rgn; } return 0; } void LFlowRegion::Insert(LFlowRect *Tr, LCss::LengthType align) { if (Tr) { Align = align; Line.Insert(Tr); } } ////////////////////////////////////////////////////////////////////// LTag::LTag(LHtml *h, LHtmlElement *p) : LHtmlElement(p), Attr(8) { Display(DispInline); Html = h; } LTag::~LTag() { if (Html->Cursor == this) { Html->Cursor = 0; } if (Html->Selection == this) { Html->Selection = 0; } DeleteObj(Ctrl); Attr.DeleteArrays(); DeleteObj(Cell); } void LTag::OnChange(PropType Prop) { } bool LTag::OnClick(const LMouse &m) { if (!Html->Environment) return false; const char *OnClick = NULL; if (Get("onclick", OnClick)) { Html->Environment->OnExecuteScript(Html, (char*)OnClick); } else { OnNotify(LNotification(m)); } return true; } void LTag::Set(const char *attr, const char *val) { char *existing = Attr.Find(attr); if (existing) DeleteArray(existing); if (val) Attr.Add(attr, NewStr(val)); } bool LTag::GetVariant(const char *Name, LVariant &Value, const char *Array) { LDomProperty Fld = LStringToDomProp(Name); switch (Fld) { case ObjStyle: // Type: LCssStyle { Value = &StyleDom; return true; } case ObjTextContent: // Type: String { Value = Text(); return true; } default: { char *a = Attr.Find(Name); if (a) { Value = a; return true; } break; } } return false; } bool LTag::SetVariant(const char *Name, LVariant &Value, const char *Array) { LDomProperty Fld = LStringToDomProp(Name); switch (Fld) { case ObjStyle: { const char *Defs = Value.Str(); if (!Defs) return false; return Parse(Defs, ParseRelaxed); } case ObjTextContent: { const char *s = Value.Str(); if (s) { LAutoWString w(CleanText(s, strlen(s), "utf-8", true, true)); Txt = w; return true; } break; } case ObjInnerHtml: // Type: String { // Clear out existing tags.. Children.DeleteObjects(); char *Doc = Value.CastString(); if (Doc) { // Create new tags... bool BackOut = false; while (Doc && *Doc) { LTag *t = new LTag(Html, this); if (t) { Doc = Html->ParseHtml(t, Doc, 1, false, &BackOut); if (!Doc) break; } else break; } } else return false; break; } default: { Set(Name, Value.CastString()); SetStyle(); break; } } Html->ViewWidth = -1; return true; } ssize_t LTag::GetTextStart() { if (PreText() && TextPos.Length() > 1) { LFlowRect *t = TextPos[1]; if (t) return t->Text - Text(); } else if (TextPos.Length() > 0) { LFlowRect *t = TextPos[0]; if (t && Text()) { LAssert(t->Text >= Text() && t->Text <= Text()+2); return t->Text - Text(); } } return 0; } static bool TextToStream(LStream &Out, char16 *Text) { if (!Text) return true; uint8_t Buf[256]; uint8_t *s = Buf; ssize_t Len = sizeof(Buf); while (*Text) { #define WriteExistingContent() \ if (s > Buf) \ Out.Write(Buf, (int)(s - Buf)); \ s = Buf; \ Len = sizeof(Buf); \ Buf[0] = 0; if (*Text == '<' || *Text == '>') { WriteExistingContent(); Out.Print("&%ct;", *Text == '<' ? 'l' : 'g'); } else if (*Text == 0xa0) { WriteExistingContent(); Out.Write((char*)" ", 6); } else { LgiUtf32To8(*Text, s, Len); if (Len < 16) { WriteExistingContent(); } } Text++; } if (s > Buf) Out.Write(Buf, s - Buf); return true; } bool LTag::CreateSource(LStringPipe &p, int Depth, bool LastWasBlock) { char *Tabs = new char[Depth+1]; memset(Tabs, '\t', Depth); Tabs[Depth] = 0; if (ValidStr(Tag)) { if (IsBlock()) { p.Print("%s%s<%s", TagId != TAG_HTML ? "\n" : "", Tabs, Tag.Get()); } else { p.Print("<%s", Tag.Get()); } if (Attr.Length()) { // const char *a; // for (char *v = Attr.First(&a); v; v = Attr.Next(&a)) for (auto v : Attr) { if (_stricmp(v.key, "style")) p.Print(" %s=\"%s\"", v.key, v.value); } } if (Props.Length()) { LCss *Css = this; LCss Tmp; #define DelProp(p) \ if (Css == this) { Tmp = *Css; Css = &Tmp; } \ Css->DeleteProp(p); // Clean out any default CSS properties where we can... LHtmlElemInfo *i = LHtmlStatic::Inst->GetTagInfo(Tag); if (i) { if (Props.Find(PropDisplay) && ( (!i->Block() && Display() == DispInline) || (i->Block() && Display() == DispBlock) )) { DelProp(PropDisplay); } switch (TagId) { default: break; case TAG_A: { LCss::ColorDef Blue(LCss::ColorRgb, Rgb32(0, 0, 255)); if (Props.Find(PropColor) && Color() == Blue) DelProp(PropColor); if (Props.Find(PropTextDecoration) && TextDecoration() == LCss::TextDecorUnderline) DelProp(PropTextDecoration) break; } case TAG_BODY: { LCss::Len FivePx(LCss::LenPx, 5.0f); if (Props.Find(PropPaddingLeft) && PaddingLeft() == FivePx) DelProp(PropPaddingLeft) if (Props.Find(PropPaddingTop) && PaddingTop() == FivePx) DelProp(PropPaddingTop) if (Props.Find(PropPaddingRight) && PaddingRight() == FivePx) DelProp(PropPaddingRight) break; } case TAG_B: { if (Props.Find(PropFontWeight) && FontWeight() == LCss::FontWeightBold) DelProp(PropFontWeight); break; } case TAG_U: { if (Props.Find(PropTextDecoration) && TextDecoration() == LCss::TextDecorUnderline) DelProp(PropTextDecoration); break; } case TAG_I: { if (Props.Find(PropFontStyle) && FontStyle() == LCss::FontStyleItalic) DelProp(PropFontStyle); break; } } } // Convert CSS props to a string and emit them... auto s = Css->ToString(); if (ValidStr(s)) { // Clean off any trailing whitespace... char *e = s ? s + strlen(s) : NULL; while (e && strchr(WhiteSpace, e[-1])) *--e = 0; // Print them to the tags attributes... p.Print(" style=\"%s\"", s.Get()); } } } if (Children.Length() || TagId == TAG_STYLE) // { if (Tag) { p.Write((char*)">", 1); TextToStream(p, Text()); } bool Last = IsBlock(); for (unsigned i=0; iCreateSource(p, Parent ? Depth+1 : 0, Last); Last = c->IsBlock(); } if (Tag) { if (IsBlock()) { if (Children.Length()) p.Print("\n%s", Tabs); } p.Print("", Tag.Get()); } } else if (Tag) { if (Text()) { p.Write((char*)">", 1); TextToStream(p, Text()); p.Print("", Tag.Get()); } else { p.Print("/>\n"); } } else { TextToStream(p, Text()); } DeleteArray(Tabs); return true; } void LTag::SetTag(const char *NewTag) { Tag.Reset(NewStr(NewTag)); if (NewTag) { Info = Html->GetTagInfo(Tag); if (Info) { TagId = Info->Id; Display(Info->Flags & LHtmlElemInfo::TI_BLOCK ? LCss::DispBlock : LCss::DispInline); } } else { Info = NULL; TagId = CONTENT; } SetStyle(); } LColour LTag::_Colour(bool f) { for (LTag *t = this; t; t = ToTag(t->Parent)) { ColorDef c = f ? t->Color() : t->BackgroundColor(); if (c.Type != ColorInherit) { return LColour(c.Rgb32, 32); } #if 1 if (!f && t->TagId == TAG_TABLE) break; #else /* This implements some basic level of colour inheritance for background colours. See test case 'cisra-cqs.html'. */ if (!f && t->TagId == TAG_TABLE) break; #endif } return LColour(); } void LTag::CopyClipboard(LMemQueue &p, bool &InSelection) { ssize_t Min = -1; ssize_t Max = -1; if (Cursor >= 0 && Selection >= 0) { Min = MIN(Cursor, Selection); Max = MAX(Cursor, Selection); } else if (InSelection) { Max = MAX(Cursor, Selection); } else { Min = MAX(Cursor, Selection); } ssize_t Off = -1; ssize_t Chars = 0; auto Start = GetTextStart(); auto t = Text() + Start; if (Min >= 0 && Max >= 0) { Off = Min + Start; Chars = Max - Min; } else if (Min >= 0) { Off = Min + Start; Chars = StrlenW(t) - Min; InSelection = true; } else if (Max >= 0) { Off = Start; Chars = Max; InSelection = false; } else if (InSelection) { Off = Start; Chars = StrlenW(t); } if (Off >= 0 && Chars > 0) { #ifdef _DEBUG for (int i=0; iCopyClipboard(p, InSelection); } } static char* _DumpColour(LCss::ColorDef c) { static char Buf[4][32]; #ifdef _MSC_VER static LONG Cur = 0; LONG Idx = InterlockedIncrement(&Cur); #else static int Cur = 0; int Idx = __sync_fetch_and_add(&Cur, 1); #endif char *b = Buf[Idx % 4]; if (c.Type == LCss::ColorInherit) strcpy_s(b, 32, "Inherit"); else sprintf_s(b, 32, "%2.2x,%2.2x,%2.2x(%2.2x)", R32(c.Rgb32),G32(c.Rgb32),B32(c.Rgb32),A32(c.Rgb32)); return b; } void LTag::_Dump(LStringPipe &Buf, int Depth) { LString Tabs; Tabs.Set(NULL, Depth); memset(Tabs.Get(), '\t', Depth); const char *Empty = ""; char *ElementName = TagId == CONTENT ? (char*)"Content" : (TagId == ROOT ? (char*)"Root" : Tag); Buf.Print( "%s%s(%p)%s%s%s (%i) Pos=%i,%i Size=%i,%i Color=%s/%s", Tabs.Get(), ElementName, this, HtmlId ? "#" : Empty, HtmlId ? HtmlId : Empty, #ifdef _DEBUG Debug ? " debug" : Empty, #else Empty, #endif WasClosed, Pos.x, Pos.y, Size.x, Size.y, _DumpColour(Color()), _DumpColour(BackgroundColor())); for (unsigned i=0; iText, Tr->Len)); if (Utf8) { size_t Len = strlen(Utf8); if (Len > 40) { Utf8[40] = 0; } } else if (Tr->Text) { Utf8.Reset(NewStr("")); } Buf.Print("Tr(%i,%i %ix%i '%s') ", Tr->x1, Tr->y1, Tr->X(), Tr->Y(), Utf8.Get()); } Buf.Print("\r\n"); for (unsigned i=0; i_Dump(Buf, Depth+1); } if (Children.Length()) { Buf.Print("%s/%s\r\n", Tabs.Get(), ElementName); } } LAutoWString LTag::DumpW() { LStringPipe Buf; // Buf.Print("Html pos=%s\n", Html?Html->GetPos().GetStr():0); _Dump(Buf, 0); LAutoString a(Buf.NewStr()); LAutoWString w(Utf8ToWide(a)); return w; } LAutoString LTag::DescribeElement() { LStringPipe s(256); s.Print("%s", Tag ? Tag.Get() : "CONTENT"); if (HtmlId) s.Print("#%s", HtmlId); for (unsigned i=0; iDefFont(); } return f; } LFont *LTag::GetFont() { if (!Font) { if (PropAddress(PropFontFamily) != 0 || FontSize().Type != LenInherit || FontStyle() != FontStyleInherit || FontVariant() != FontVariantInherit || FontWeight() != FontWeightInherit || TextDecoration() != TextDecorInherit) { LCss c; LCss::PropMap Map; Map.Add(PropFontFamily, new LCss::PropArray); Map.Add(PropFontSize, new LCss::PropArray); Map.Add(PropFontStyle, new LCss::PropArray); Map.Add(PropFontVariant, new LCss::PropArray); Map.Add(PropFontWeight, new LCss::PropArray); Map.Add(PropTextDecoration, new LCss::PropArray); for (LTag *t = this; t; t = ToTag(t->Parent)) { if (t->TagId == TAG_IFRAME) break; if (!c.InheritCollect(*t, Map)) break; } c.InheritResolve(Map); Map.DeleteObjects(); if ((Font = Html->FontCache->GetFont(&c))) return Font; } else { LTag *t = this; while (!t->Font && t->Parent) { t = ToTag(t->Parent); } if (t->Font) return t->Font; } Font = Html->DefFont(); } return Font; } LTag *LTag::PrevTag() { if (Parent) { ssize_t i = Parent->Children.IndexOf(this); if (i >= 0) { return ToTag(Parent->Children[i - 1]); } } return 0; } void LTag::Invalidate() { LRect p = GetRect(); for (LTag *t=ToTag(Parent); t; t=ToTag(t->Parent)) { p.Offset(t->Pos.x, t->Pos.y); } Html->Invalidate(&p); } LTag *LTag::IsAnchor(LString *Uri) { LTag *a = 0; for (LTag *t = this; t; t = ToTag(t->Parent)) { if (t->TagId == TAG_A) { a = t; break; } } if (a && Uri) { const char *u = 0; if (a->Get("href", u)) { LAutoWString w(CleanText(u, strlen(u), "utf-8")); if (w) { *Uri = w; } } } return a; } bool LTag::OnMouseClick(LMouse &m) { bool Processed = false; if (m.IsContextMenu()) { LString Uri; const char *ImgSrc = NULL; LTag *a = IsAnchor(&Uri); bool IsImg = TagId == TAG_IMG; if (IsImg) Get("src", ImgSrc); bool IsAnchor = a && ValidStr(Uri); if (IsAnchor || IsImg) { LSubMenu RClick; #define IDM_COPY_LINK 100 #define IDM_COPY_IMG 101 if (Html->GetMouse(m, true)) { int Id = 0; if (IsAnchor) RClick.AppendItem(LLoadString(L_COPY_LINK_LOCATION, "&Copy Link Location"), IDM_COPY_LINK, Uri != NULL); if (IsImg) RClick.AppendItem("Copy Image Location", IDM_COPY_IMG, ImgSrc != NULL); if (Html->GetEnv()) Html->GetEnv()->AppendItems(&RClick, Uri); switch (Id = RClick.Float(Html, m.x, m.y)) { case IDM_COPY_LINK: { LClipBoard Clip(Html); Clip.Text(Uri); break; } case IDM_COPY_IMG: { LClipBoard Clip(Html); Clip.Text(ImgSrc); break; } default: { if (Html->GetEnv()) Html->GetEnv()->OnMenu(Html, Id, a); break; } } } Processed = true; } } else if (m.Down() && m.Left()) { #ifdef _DEBUG if (m.Ctrl()) { auto Style = ToString(); LStringPipe p(256); p.Print("Tag: %s\n", Tag ? Tag.Get() : "CONTENT"); if (Class.Length()) { p.Print("Class(es): "); for (unsigned i=0; iParent; t=ToTag(t->Parent)) { LStringPipe Tmp; Tmp.Print(" %s", t->Tag ? t->Tag.Get() : "CONTENT"); if (t->HtmlId) { Tmp.Print("#%s", t->HtmlId); } for (unsigned i=0; iClass.Length(); i++) { Tmp.Print(".%s", t->Class[i].Get()); } LAutoString Txt(Tmp.NewStr()); p.Print("%s", Txt.Get()); LDisplayString Ds(LSysFont, Txt); int Px = 170 - Ds.X(); int Chars = Px / Sp.X(); for (int c=0; cPos.x, t->Pos.y, t->Size.x, t->Size.y); } LAutoString a(p.NewStr()); LgiMsg( Html, "%s", Html->GetClass(), MB_OK, a.Get()); } else #endif { LString Uri; if (Html && Html->Environment) { if (IsAnchor(&Uri)) { if (Uri) { if (!Html->d->LinkDoubleClick || m.Double()) { Html->Environment->OnNavigate(Html, Uri); Processed = true; } } const char *OnClk = NULL; if (!Processed && Get("onclick", OnClk)) { Html->Environment->OnExecuteScript(Html, (char*)OnClk); } } else { Processed = OnClick(m); } } } } return Processed; } LTag *LTag::GetBlockParent(ssize_t *Idx) { if (IsBlock()) { if (Idx) *Idx = 0; return this; } for (LTag *t = this; t; t = ToTag(t->Parent)) { if (ToTag(t->Parent)->IsBlock()) { if (Idx) { *Idx = t->Parent->Children.IndexOf(t); } return ToTag(t->Parent); } } return 0; } LTag *LTag::GetAnchor(char *Name) { if (!Name) return 0; const char *n; if (IsAnchor(0) && Get("name", n) && n && !_stricmp(Name, n)) { return this; } for (unsigned i=0; iGetAnchor(Name); if (Result) return Result; } return 0; } LTag *LTag::GetTagByName(const char *Name) { if (Name) { if (Tag && _stricmp(Tag, Name) == 0) { return this; } for (unsigned i=0; iGetTagByName(Name); if (Result) return Result; } } return 0; } static int IsNearRect(LRect *r, int x, int y) { if (r->Overlap(x, y)) { return 0; } else if (x >= r->x1 && x <= r->x2) { if (y < r->y1) return r->y1 - y; else return y - r->y2; } else if (y >= r->y1 && y <= r->y2) { if (x < r->x1) return r->x1 - x; else return x - r->x2; } int64 dx = 0; int64 dy = 0; if (x < r->x1) { if (y < r->y1) { // top left dx = r->x1 - x; dy = r->y1 - y; } else { // bottom left dx = r->x1 - x; dy = y - r->y2; } } else { if (y < r->y1) { // top right dx = x - r->x2; dy = r->y1 - y; } else { // bottom right dx = x - r->x2; dy = y - r->y2; } } return (int) sqrt( (double) ( (dx * dx) + (dy * dy) ) ); } ssize_t LTag::NearestChar(LFlowRect *Tr, int x, int y) { LFont *f = GetFont(); if (f) { LDisplayString ds(f, Tr->Text, Tr->Len); ssize_t c = ds.CharAt(x - Tr->x1); if (Tr->Text == PreText()) { return 0; } else { char16 *t = Tr->Text + c; size_t Len = StrlenW(Text()); if (t >= Text() && t <= Text() + Len) { return (t - Text()) - GetTextStart(); } else { LgiTrace("%s:%i - Error getting char at position.\n", _FL); } } } return -1; } void LTag::GetTagByPos(LTagHit &TagHit, int x, int y, int Depth, bool InBody, bool DebugLog) { /* InBody: Originally I had this test in the code but it seems that some test cases have actual content after the body. And testing for "InBody" breaks functionality for those cases (see "spam4.html" and the unsubscribe link at the end of the doc). */ if (TagId == TAG_IMG) { LRect img(0, 0, Size.x - 1, Size.y - 1); if (/*InBody &&*/ img.Overlap(x, y)) { TagHit.Direct = this; TagHit.Block = 0; } } else if (/*InBody &&*/ TextPos.Length()) { for (unsigned i=0; i= Tr->y1 && y <= Tr->y2; int Near = IsNearRect(Tr, x, y); if (Near >= 0 && Near < 100) { if ( !TagHit.NearestText || ( SameRow && !TagHit.NearSameRow ) || ( SameRow == TagHit.NearSameRow && Near < TagHit.Near ) ) { TagHit.NearestText = this; TagHit.NearSameRow = SameRow; TagHit.Block = Tr; TagHit.Near = Near; TagHit.Index = NearestChar(Tr, x, y); if (DebugLog) { LgiTrace("%i:GetTagByPos HitText %s #%s, idx=%i, near=%i, txt='%S'\n", Depth, Tag.Get(), HtmlId, TagHit.Index, TagHit.Near, Tr->Text); } if (!TagHit.Near) { TagHit.Direct = this; TagHit.LocalCoords.x = x; TagHit.LocalCoords.y = y; } } } } } else if ( TagId != TAG_TR && Tag && x >= 0 && y >= 0 && x < Size.x && y < Size.y // && InBody ) { // Direct hit TagHit.Direct = this; TagHit.LocalCoords.x = x; TagHit.LocalCoords.y = y; if (DebugLog) { LgiTrace("%i:GetTagByPos DirectHit %s #%s, idx=%i, near=%i\n", Depth, Tag.Get(), HtmlId, TagHit.Index, TagHit.Near); } } if (TagId == TAG_BODY) InBody = true; for (unsigned i=0; iPos.x >= 0 && t->Pos.y >= 0) { t->GetTagByPos(TagHit, x - t->Pos.x, y - t->Pos.y, Depth + 1, InBody, DebugLog); } } } int LTag::OnNotify(LNotification n) { if (!Ctrl || !Html->InThread()) return 0; switch (CtrlType) { case CtrlSubmit: { LTag *Form = this; while (Form && Form->TagId != TAG_FORM) Form = ToTag(Form->Parent); if (Form) Html->OnSubmitForm(Form); break; } default: { CtrlValue = Ctrl->Name(); break; } } return 0; } void LTag::CollectFormValues(LHashTbl,char*> &f) { if (CtrlType != CtrlNone) { const char *Name; if (Get("name", Name)) { char *Existing = f.Find(Name); if (Existing) DeleteArray(Existing); char *Val = CtrlValue.Str(); if (Val) { LStringPipe p(256); for (char *v = Val; *v; v++) { if (*v == ' ') p.Write("+", 1); else if (IsAlpha(*v) || IsDigit(*v) || *v == '_' || *v == '.') p.Write(v, 1); else p.Print("%%%02.2X", *v); } f.Add(Name, p.NewStr()); } else { f.Add(Name, NewStr("")); } } } for (unsigned i=0; iCollectFormValues(f); } } LTag *LTag::FindCtrlId(int Id) { if (Ctrl && Ctrl->GetId() == Id) return this; for (unsigned i=0; iFindCtrlId(Id); if (f) return f; } return NULL; } void LTag::Find(int TagType, LArray &Out) { if (TagId == TagType) { Out.Add(this); } for (unsigned i=0; iFind(TagType, Out); } } void LTag::SetImage(const char *Uri, LSurface *Img) { if (Img) { if (TagId != TAG_IMG) { ImageDef *Def = (ImageDef*)LCss::Props.Find(PropBackgroundImage); if (Def) { Def->Type = ImageOwn; DeleteObj(Def->Img); Def->Img = Img; } } else { if (Img->GetColourSpace() == CsIndex8) { if (Image.Reset(new LMemDC(Img->X(), Img->Y(), System32BitColourSpace))) { Image->Colour(0, 32); Image->Rectangle(); Image->Blt(0, 0, Img); } else LgiTrace("%s:%i - SetImage can't promote 8bit image to 32bit.\n", _FL); } else Image.Reset(Img); LRect r = XSubRect(); if (r.Valid()) { LAutoPtr t(new LMemDC(r.X(), r.Y(), Image->GetColourSpace())); if (t) { t->Blt(0, 0, Image, &r); Image = t; } } } for (unsigned i=0; iCell) { t->Cell->MinContent = 0; t->Cell->MaxContent = 0; } } } else { Html->d->Loading.Add(Uri, this); } } void LTag::LoadImage(const char *Uri) { #if DOCUMENT_LOAD_IMAGES if (!Html->Environment) return; LUri u(Uri); bool LdImg = Html->GetLoadImages(); bool IsRemote = u.sProtocol && ( !_stricmp(u.sProtocol, "http") || !_stricmp(u.sProtocol, "https") || !_stricmp(u.sProtocol, "ftp") ); if (IsRemote && !LdImg) { Html->NeedsCapability("RemoteContent"); return; } else if (u.IsProtocol("data")) { if (!u.sPath) return; const char *s = u.sPath; if (*s++ != '/') return; LAutoString Type(LTokStr(s)); if (*s++ != ',') return; auto p = LString(Type).SplitDelimit(",;:"); if (p.Length() != 2 || !p.Last().Equals("base64")) return; LString Name = LString("name.") + p[0]; auto Filter = LFilterFactory::New(Name, FILTER_CAP_READ, NULL); if (!Filter) return; auto slen = strlen(s); auto blen = BufferLen_64ToBin(slen); LMemStream bin; bin.SetSize(blen); ConvertBase64ToBinary((uint8_t*)bin.GetBasePtr(), blen, s, slen); bin.SetPos(0); if (!Image.Reset(new LMemDC)) return; auto result = Filter->ReadImage(Image, &bin); if (result != LFilter::IoSuccess) Image.Reset(); return; } LDocumentEnv::LoadJob *j = Html->Environment->NewJob(); if (j) { LAssert(Html != NULL); j->Uri.Reset(NewStr(Uri)); j->Env = Html->Environment; j->UserData = this; j->UserUid = Html->GetDocumentUid(); // LgiTrace("%s:%i - new job %p, %p\n", _FL, j, j->UserData); LDocumentEnv::LoadType Result = Html->Environment->GetContent(j); if (Result == LDocumentEnv::LoadImmediate) { SetImage(Uri, j->pDC.Release()); } else if (Result == LDocumentEnv::LoadDeferred) { Html->d->DeferredLoads++; } DeleteObj(j); } #endif } void LTag::LoadImages() { const char *Uri = 0; if (Html->Environment && TagId == TAG_IMG && !Image) { if (Get("src", Uri)) LoadImage(Uri); } for (unsigned i=0; iLoadImages(); } } void LTag::ImageLoaded(char *uri, LSurface *Img, int &Used) { const char *Uri = 0; if (!Image && Get("src", Uri)) { if (strcmp(Uri, uri) == 0) { if (Used == 0) { SetImage(Uri, Img); } else { SetImage(Uri, new LMemDC(Img)); } Used++; } } for (unsigned i=0; iImageLoaded(uri, Img, Used); } } struct LTagElementCallback : public LCss::ElementCallback { const char *Val; const char *GetElement(LTag *obj) { return obj->Tag; } const char *GetAttr(LTag *obj, const char *Attr) { if (obj->Get(Attr, Val)) return Val; return NULL; } bool GetClasses(LString::Array &Classes, LTag *obj) { Classes = obj->Class; return Classes.Length() > 0; } LTag *GetParent(LTag *obj) { return ToTag(obj->Parent); } LArray GetChildren(LTag *obj) { LArray c; for (unsigned i=0; iChildren.Length(); i++) c.Add(ToTag(obj->Children[i])); return c; } }; void LTag::RestyleAll() { Restyle(); for (unsigned i=0; iRestyleAll(); } } // After CSS has changed this function scans through the CSS and applies any rules // that match the current tag. void LTag::Restyle() { // Use the matching built into the LCss Store. LCss::SelArray Styles; LTagElementCallback Context; if (Html->CssStore.Match(Styles, &Context, this)) { for (unsigned i=0; iStyle); } } // Do the element specific styles const char *s; if (Get("style", s)) SetCssStyle(s); #if DEBUG_RESTYLE && defined(_DEBUG) if (Debug) { auto Style = ToString(); LgiTrace(">>>> %s <<<<:\n%s\n\n", Tag.Get(), Style.Get()); } #endif } void LTag::SetStyle() { const static float FntMul[] = { 0.6f, // size=1 0.89f, // size=2 1.0f, // size=3 1.2f, // size=4 1.5f, // size=5 2.0f, // size=6 3.0f // size=7 }; const char *s = 0; #ifdef _DEBUG if (Get("debug", s)) { if ((Debug = atoi(s))) { LgiTrace("Debug Tag: %p '%s'\n", this, Tag ? Tag.Get() : "CONTENT"); } } #endif if (Get("Color", s)) { ColorDef Def; if (LHtmlParser::ParseColour(s, Def)) { Color(Def); } } if (Get("Background", s) || Get("bgcolor", s)) { ColorDef Def; if (LHtmlParser::ParseColour(s, Def)) { BackgroundColor(Def); } else { LCss::ImageDef Img; Img.Type = ImageUri; Img.Uri = s; BackgroundImage(Img); BackgroundRepeat(RepeatBoth); } } switch (TagId) { default: { if (!Stricmp(Tag.Get(), "o:p")) Display(LCss::DispNone); break; } case TAG_LINK: { const char *Type, *Href; if (Html->Environment && Get("type", Type) && Get("href", Href) && !Stricmp(Type, "text/css") && !Html->CssHref.Find(Href)) { LDocumentEnv::LoadJob *j = Html->Environment->NewJob(); if (j) { LAssert(Html != NULL); LTag *t = this; j->Uri.Reset(NewStr(Href)); j->Env = Html->Environment; j->UserData = t; j->UserUid = Html->GetDocumentUid(); LDocumentEnv::LoadType Result = Html->Environment->GetContent(j); if (Result == LDocumentEnv::LoadImmediate) { LStreamI *s = j->GetStream(); if (s) { int Len = (int)s->GetSize(); if (Len > 0) { LAutoString a(new char[Len+1]); ssize_t r = s->Read(a, Len); a[r] = 0; Html->CssHref.Add(Href, true); Html->OnAddStyle("text/css", a); } } } else if (Result == LDocumentEnv::LoadDeferred) { Html->d->DeferredLoads++; } DeleteObj(j); } } break; } case TAG_BLOCKQUOTE: { MarginTop(Len("8px")); MarginBottom(Len("8px")); MarginLeft(Len("16px")); if (Get("Type", s)) { if (_stricmp(s, "cite") == 0) { BorderLeft(BorderDef(this, "1px solid blue")); PaddingLeft(Len("0.5em")); /* ColorDef Def; Def.Type = ColorRgb; Def.Rgb32 = Rgb32(0x80, 0x80, 0x80); Color(Def); */ } } break; } case TAG_P: { MarginBottom(Len("1em")); break; } case TAG_A: { const char *Href; if (Get("href", Href)) { ColorDef c; c.Type = ColorRgb; c.Rgb32 = Rgb32(0, 0, 255); Color(c); TextDecoration(TextDecorUnderline); } break; } case TAG_TABLE: { Len l; if (!Cell) Cell = new TblCell; if (Get("border", s)) { BorderDef b; if (b.Parse(this, s)) { BorderLeft(b); BorderRight(b); BorderTop(b); BorderBottom(b); } } if (Get("cellspacing", s) && l.Parse(s, PropBorderSpacing, ParseRelaxed)) { BorderSpacing(l); } else { // BorderSpacing(LCss::Len(LCss::LenPx, 2.0f)); } if (Get("cellpadding", s) && l.Parse(s, Prop_CellPadding, ParseRelaxed)) { _CellPadding(l); } if (Get("align", s)) { Len l; if (l.Parse(s)) Cell->XAlign = l.Type; } break; } case TAG_TD: case TAG_TH: { if (!Cell) Cell = new TblCell; LTag *Table = GetTable(); if (Table) { Len l = Table->_CellPadding(); if (!l.IsValid()) { l.Type = LCss::LenPx; l.Value = DefaultCellPadding; } PaddingLeft(l); PaddingRight(l); PaddingTop(l); PaddingBottom(l); } if (TagId == TAG_TH) FontWeight(LCss::FontWeightBold); break; } case TAG_BODY: { MarginLeft(Len(Get("leftmargin", s) ? s : DefaultBodyMargin)); MarginTop(Len(Get("topmargin", s) ? s : DefaultBodyMargin)); MarginRight(Len(Get("rightmargin", s) ? s : DefaultBodyMargin)); if (Get("text", s)) { ColorDef c; if (c.Parse(s)) { Color(c); } } break; } case TAG_OL: case TAG_UL: { MarginLeft(Len("16px")); break; } case TAG_STRONG: case TAG_B: { FontWeight(FontWeightBold); break; } case TAG_I: { FontStyle(FontStyleItalic); break; } case TAG_U: { TextDecoration(TextDecorUnderline); break; } case TAG_SUP: { VerticalAlign(VerticalSuper); FontSize(SizeSmaller); break; } case TAG_SUB: { VerticalAlign(VerticalSub); FontSize(SizeSmaller); break; } case TAG_TITLE: { Display(LCss::DispNone); break; } } if (Get("width", s)) { Len l; if (l.Parse(s, PropWidth, ParseRelaxed)) { Width(l); } } if (Get("height", s)) { Len l; if (l.Parse(s, PropHeight, ParseRelaxed)) Height(l); } if (Get("align", s)) { if (_stricmp(s, "left") == 0) TextAlign(Len(AlignLeft)); else if (_stricmp(s, "right") == 0) TextAlign(Len(AlignRight)); else if (_stricmp(s, "center") == 0) TextAlign(Len(AlignCenter)); } if (Get("valign", s)) { if (_stricmp(s, "top") == 0) VerticalAlign(Len(VerticalTop)); else if (_stricmp(s, "middle") == 0) VerticalAlign(Len(VerticalMiddle)); else if (_stricmp(s, "bottom") == 0) VerticalAlign(Len(VerticalBottom)); } Get("id", HtmlId); if (Get("class", s)) { Class = LString(s).SplitDelimit(" \t"); } Restyle(); switch (TagId) { default: break; case TAG_BIG: { LCss::Len l; l.Type = SizeLarger; FontSize(l); break; } /* case TAG_META: { LAutoString Cs; const char *s; if (Get("http-equiv", s) && _stricmp(s, "Content-Type") == 0) { const char *ContentType; if (Get("content", ContentType)) { char *CharSet = stristr(ContentType, "charset="); if (CharSet) { char16 *cs = NULL; Html->ParsePropValue(CharSet + 8, cs); Cs.Reset(WideToUtf8(cs)); DeleteArray(cs); } } } if (Get("name", s) && _stricmp(s, "charset") == 0 && Get("content", s)) { Cs.Reset(NewStr(s)); } else if (Get("charset", s)) { Cs.Reset(NewStr(s)); } if (Cs) { if (Cs && _stricmp(Cs, "utf-16") != 0 && _stricmp(Cs, "utf-32") != 0 && LGetCsInfo(Cs)) { // Html->SetCharset(Cs); } } break; } */ case TAG_BODY: { LCss::ColorDef Bk = BackgroundColor(); if (Bk.Type != ColorInherit) { // Copy the background up to the LHtml wrapper Html->GetCss(true)->BackgroundColor(Bk); } /* LFont *f = GetFont(); if (FontSize().Type == LenInherit) { FontSize(Len(LenPt, (float)f->PointSize())); } */ break; } case TAG_HEAD: { Display(DispNone); break; } case TAG_PRE: { LFontType Type; if (Type.GetSystemFont("Fixed")) { LAssert(ValidStr(Type.GetFace())); FontFamily(StringsDef(Type.GetFace())); } break; } case TAG_TR: break; case TAG_TD: case TAG_TH: { LAssert(Cell != NULL); const char *s; if (Get("colspan", s)) Cell->Span.x = atoi(s); else Cell->Span.x = 1; if (Get("rowspan", s)) Cell->Span.y = atoi(s); else Cell->Span.y = 1; Cell->Span.x = MAX(Cell->Span.x, 1); Cell->Span.y = MAX(Cell->Span.y, 1); if (Display() == DispInline || Display() == DispInlineBlock) { Display(DispBlock); // Inline-block TD??? Nope. } break; } case TAG_IMG: { const char *Uri; if (Html->Environment && Get("src", Uri)) { // printf("Uri: %s\n", Uri); LoadImage(Uri); } break; } case TAG_H1: { char s[32]; sprintf_s(s, sizeof(s), "%ipt", (int)((float)Html->DefFont()->PointSize() * FntMul[5])); FontSize(Len(s)); FontWeight(FontWeightBold); break; } case TAG_H2: { char s[32]; sprintf_s(s, sizeof(s), "%ipt", (int)((float)Html->DefFont()->PointSize() * FntMul[4])); FontSize(Len(s)); FontWeight(FontWeightBold); break; } case TAG_H3: { char s[32]; sprintf_s(s, sizeof(s), "%ipt", (int)((float)Html->DefFont()->PointSize() * FntMul[3])); FontSize(Len(s)); FontWeight(FontWeightBold); break; } case TAG_H4: { char s[32]; sprintf_s(s, sizeof(s), "%ipt", (int)((float)Html->DefFont()->PointSize() * FntMul[2])); FontSize(Len(s)); FontWeight(FontWeightBold); break; } case TAG_H5: { char s[32]; sprintf_s(s, sizeof(s), "%ipt", (int)((float)Html->DefFont()->PointSize() * FntMul[1])); FontSize(Len(s)); FontWeight(FontWeightBold); break; } case TAG_H6: { char s[32]; sprintf_s(s, sizeof(s), "%ipt", (int)((float)Html->DefFont()->PointSize() * FntMul[0])); FontSize(Len(s)); FontWeight(FontWeightBold); break; } case TAG_FONT: { const char *s = 0; if (Get("Face", s)) { LAutoWString cw(CleanText(s, strlen(s), "utf-8", true)); LAutoString c8(WideToUtf8(cw)); auto Faces = LString(c8).SplitDelimit(","); auto Face = Faces[0].Strip(); if (ValidStr(Face)) FontFamily(c8.Get()); else LgiTrace("%s:%i - No face for font tag.\n", _FL); } if (Get("Size", s)) { bool Digit = false, NonW = false; for (auto *c = s; *c; c++) { if (IsDigit(*c) || *c == '-') Digit = true; else if (!IsWhiteSpace(*c)) NonW = true; } if (Digit && !NonW) { auto Sz = atoi(s); switch (Sz) { case 1: FontSize(Len(LCss::LenEm, 0.63f)); break; case 2: FontSize(Len(LCss::LenEm, 0.82f)); break; case 3: FontSize(Len(LCss::LenEm, 1.0f)); break; case 4: FontSize(Len(LCss::LenEm, 1.13f)); break; case 5: FontSize(Len(LCss::LenEm, 1.5f)); break; case 6: FontSize(Len(LCss::LenEm, 2.0f)); break; case 7: FontSize(Len(LCss::LenEm, 3.0f)); break; } } else { FontSize(Len(s)); } } break; } case TAG_SELECT: { if (!Html->InThread()) break; LAssert(!Ctrl); Ctrl = new LCombo(Html->d->NextCtrlId++, 0, 0, 100, LSysFont->GetHeight() + 8, NULL); CtrlType = CtrlSelect; break; } case TAG_INPUT: { if (!Html->InThread()) break; LAssert(!Ctrl); const char *Type, *Value = NULL; Get("value", Value); LAutoWString CleanValue(Value ? CleanText(Value, strlen(Value), "utf-8", true, true) : NULL); if (CleanValue) { CtrlValue = CleanValue; } if (Get("type", Type)) { if (!_stricmp(Type, "password")) CtrlType = CtrlPassword; else if (!_stricmp(Type, "email")) CtrlType = CtrlEmail; else if (!_stricmp(Type, "text")) CtrlType = CtrlText; else if (!_stricmp(Type, "button")) CtrlType = CtrlButton; else if (!_stricmp(Type, "submit")) CtrlType = CtrlSubmit; else if (!_stricmp(Type, "hidden")) CtrlType = CtrlHidden; DeleteObj(Ctrl); if (CtrlType == CtrlEmail || CtrlType == CtrlText || CtrlType == CtrlPassword) { LEdit *Ed; LAutoString UtfCleanValue(WideToUtf8(CleanValue)); Ctrl = Ed = new LEdit(Html->d->NextCtrlId++, 0, 0, 60, LSysFont->GetHeight() + 8, UtfCleanValue); if (Ctrl) { Ed->Sunken(false); Ed->Password(CtrlType == CtrlPassword); } } else if (CtrlType == CtrlButton || CtrlType == CtrlSubmit) { LAutoString UtfCleanValue(WideToUtf8(CleanValue)); if (UtfCleanValue) { Ctrl = new InputButton(this, Html->d->NextCtrlId++, UtfCleanValue); } } } break; } } if (IsBlock()) { LCss::ImageDef bk = BackgroundImage(); if (bk.Type == LCss::ImageUri && ValidStr(bk.Uri) && !bk.Uri.Equals("transparent")) { LoadImage(bk.Uri); } } if (Ctrl) { LFont *f = GetFont(); if (f) Ctrl->SetFont(f, false); } } void LTag::OnStyleChange(const char *name) { if (!Stricmp(name, "display") && Html) { Html->Layout(true); Html->Invalidate(); } } void LTag::SetCssStyle(const char *Style) { if (Style) { // Strip out comments char *Comment = NULL; while ((Comment = strstr((char*)Style, "/*"))) { char *End = strstr(Comment+2, "*/"); if (!End) break; for (char *c = Comment; cDocCharSet && Html->Charset) { DocAndCsTheSame = _stricmp(Html->DocCharSet, Html->Charset) == 0; } if (SourceCs) { t = (char16*) LNewConvertCp(LGI_WideCharset, s, SourceCs, Len); } else if (Html->DocCharSet && Html->Charset && !DocAndCsTheSame && !Html->OverideDocCharset) { char *DocText = (char*)LNewConvertCp(Html->DocCharSet, s, Html->Charset, Len); t = (char16*) LNewConvertCp(LGI_WideCharset, DocText, Html->DocCharSet, -1); DeleteArray(DocText); } else if (Html->DocCharSet) { t = (char16*) LNewConvertCp(LGI_WideCharset, s, Html->DocCharSet, Len); } else { t = (char16*) LNewConvertCp(LGI_WideCharset, s, Html->Charset.Get() ? Html->Charset.Get() : DefaultCs, Len); } if (t && ConversionAllowed) { char16 *o = t; for (char16 *i=t; *i; ) { switch (*i) { case '&': { i++; if (*i == '#') { // Unicode Number char n[32] = "", *p = n; i++; if (*i == 'x' || *i == 'X') { // Hex number i++; while ( *i && ( IsDigit(*i) || (*i >= 'A' && *i <= 'F') || (*i >= 'a' && *i <= 'f') ) && (p - n) < 31) { *p++ = (char)*i++; } } else { // Decimal number while (*i && IsDigit(*i) && (p - n) < 31) { *p++ = (char)*i++; } } *p++ = 0; char16 Ch = atoi(n); if (Ch) { *o++ = Ch; } if (*i && *i != ';') i--; } else { // Named Char char16 *e = i; while (*e && IsAlpha(*e) && *e != ';') { e++; } LAutoWString Var(NewStrW(i, e-i)); char16 Char = LHtmlStatic::Inst->VarMap.Find(Var); if (Char) { *o++ = Char; i = e; } else { i--; *o++ = *i; } } break; } case '\r': { break; } case ' ': case '\t': case '\n': { if (KeepWhiteSpace) { *o++ = *i; } else { *o++ = ' '; // Skip furthur whitespace while (i[1] && IsWhiteSpace(i[1])) { i++; } } break; } default: { // Normal char *o++ = *i; break; } } if (*i) i++; else break; } *o++ = 0; } if (t && !*t) { DeleteArray(t); } return t; } char *LTag::ParseText(char *Doc) { ColorDef c; c.Type = ColorRgb; c.Rgb32 = LColour(L_WORKSPACE).c32(); BackgroundColor(c); TagId = TAG_BODY; Tag.Reset(NewStr("body")); Info = Html->GetTagInfo(Tag); char *OriginalCp = NewStr(Html->Charset); LStringPipe Utf16; char *s = Doc; while (s) { if (*s == '\r') { s++; } else if (*s == '<') { // Process tag char *e = s; e++; while (*e && *e != '>') { if (*e == '\"' || *e == '\'') { char *q = strchr(e + 1, *e); if (q) e = q + 1; else e++; } else e++; } if (*e == '>') e++; // Output tag Html->SetCharset("iso-8859-1"); char16 *t = CleanText(s, e - s, NULL, false); if (t) { Utf16.Push(t); DeleteArray(t); } s = e; } else if (!*s || *s == '\n') { // Output previous line char16 *Line = Utf16.NewStrW(); if (Line) { LTag *t = new LTag(Html, this); if (t) { t->Color(LColour(L_TEXT)); t->Text(Line); } } if (*s == '\n') { s++; LTag *t = new LTag(Html, this); if (t) { t->TagId = TAG_BR; t->Tag.Reset(NewStr("br")); t->Info = Html->GetTagInfo(t->Tag); } } else break; } else { // Seek end of text char *e = s; while (*e && *e != '\r' && *e != '\n' && *e != '<') e++; // Output text Html->SetCharset(OriginalCp); LAutoWString t(CleanText(s, e - s, NULL, false)); if (t) { Utf16.Push(t); } s = e; } } Html->SetCharset(OriginalCp); DeleteArray(OriginalCp); return 0; } bool LTag::ConvertToText(TextConvertState &State) { const static char *Rule = "------------------------------------------------------"; int DepthInc = 0; switch (TagId) { default: break; case TAG_P: if (State.GetPrev()) State.NewLine(); break; case TAG_UL: case TAG_OL: DepthInc = 2; break; } if (TagId != TAG_SCRIPT && TagId != TAG_STYLE && ValidStrW(Txt)) { for (int i=0; iConvertToUnicode(Txt); else u.Reset(WideToUtf8(Txt)); if (u) { size_t u_len = strlen(u); State.Write(u, u_len); } } State.Depth += DepthInc; for (unsigned i=0; iConvertToText(State); } State.Depth -= DepthInc; if (IsBlock()) { if (State.CharsOnLine) State.NewLine(); } else { switch (TagId) { case TAG_A: { // Emit the link to the anchor if it's different from the text of the span... const char *Href; if (Get("href", Href) && ValidStrW(Txt)) { if (_strnicmp(Href, "mailto:", 7) == 0) Href += 7; size_t HrefLen = strlen(Href); LAutoWString h(CleanText(Href, HrefLen, "utf-8")); if (h && StrcmpW(h, Txt) != 0) { // Href different from the text of the link State.Write(" (", 2); State.Write(Href, HrefLen); State.Write(")", 1); } } break; } case TAG_HR: { State.Write(Rule, strlen(Rule)); State.NewLine(); break; } case TAG_BR: { State.NewLine(); break; } default: break; } } return true; } char *LTag::NextTag(char *s) { while (s && *s) { char *n = strchr(s, '<'); if (n) { if (!n[1]) return NULL; if (IsAlpha(n[1]) || strchr("!/", n[1]) || n[1] == '?') { return n; } s = n + 1; } else break; } return 0; } void LHtml::CloseTag(LTag *t) { if (!t) return; OpenTags.Delete(t); } bool LTag::OnUnhandledColor(LCss::ColorDef *def, const char *&s) { const char *e = s; while (*e && (IsText(*e) || *e == '_')) e++; char tmp[256]; ssize_t len = e - s; memcpy(tmp, s, len); tmp[len] = 0; int m = LHtmlStatic::Inst->ColourMap.Find(tmp); s = e; if (m >= 0) { def->Type = LCss::ColorRgb; def->Rgb32 = Rgb24To32(m); return true; } return false; } void LTag::ZeroTableElements() { if (TagId == TAG_TABLE || TagId == TAG_TR || IsTableCell(TagId)) { Size.x = 0; Size.y = 0; if (Cell) { Cell->MinContent = 0; Cell->MaxContent = 0; } for (unsigned i=0; iZeroTableElements(); } } } void LTag::ResetCaches() { /* If during the parse process a callback causes a layout to happen then it's possible to have partial information in the LHtmlTableLayout structure, like missing TD cells. Because they haven't been parsed yet. This is called at the end of the parsing to reset all the cached info in LHtmlTableLayout. That way when the first real layout happens all the data is there. */ if (Cell) DeleteObj(Cell->Cells); for (size_t i=0; iResetCaches(); } LPoint LTag::GetTableSize() { LPoint s(0, 0); if (Cell && Cell->Cells) { Cell->Cells->GetSize(s.x, s.y); } return s; } LTag *LTag::GetTableCell(int x, int y) { LTag *t = this; while ( t && !t->Cell && !t->Cell->Cells && t->Parent) { t = ToTag(t->Parent); } if (t && t->Cell && t->Cell->Cells) { return t->Cell->Cells->Get(x, y); } return 0; } // This function gets the largest and smallest piece of content // in this cell and all it's children. bool LTag::GetWidthMetrics(LTag *Table, uint16 &Min, uint16 &Max) { bool Status = true; int MarginPx = 0; int LineWidth = 0; if (Display() == LCss::DispNone) return true; // Break the text into words and measure... if (Text()) { int MinContent = 0; int MaxContent = 0; LFont *f = GetFont(); if (f) { for (char16 *s = Text(); s && *s; ) { // Skip whitespace... while (*s && StrchrW(WhiteW, *s)) s++; // Find end of non-whitespace char16 *e = s; while (*e && !StrchrW(WhiteW, *e)) e++; // Find size of the word ssize_t Len = e - s; if (Len > 0) { LDisplayString ds(f, s, Len); MinContent = MAX(MinContent, ds.X()); } // Move to the next word. s = (*e) ? e + 1 : 0; } LDisplayString ds(f, Text()); LineWidth = MaxContent = ds.X(); } #if 0//def _DEBUG if (Debug) { LgiTrace("GetWidthMetrics Font=%p Sz=%i,%i\n", f, MinContent, MaxContent); } #endif Min = MAX(Min, MinContent); Max = MAX(Max, MaxContent); } // Specific tag handling? switch (TagId) { default: { if (IsBlock()) { MarginPx = (int)(BorderLeft().ToPx() + BorderRight().ToPx() + PaddingLeft().ToPx() + PaddingRight().ToPx()); } break; } case TAG_IMG: { Len w = Width(); if (w.IsValid()) { int x = (int) w.Value; Min = MAX(Min, x); Max = MAX(Max, x); } else if (Image) { Min = Max = Image->X(); } else { Size.x = Size.y = DefaultImgSize; Min = MAX(Min, Size.x); Max = MAX(Max, Size.x); } break; } case TAG_TD: case TAG_TH: { Len w = Width(); if (w.IsValid()) { if (w.IsDynamic()) { Min = MAX(Min, (int)w.Value); Max = MAX(Max, (int)w.Value); } else { Max = w.ToPx(0, GetFont()); } } else { LCss::BorderDef BLeft = BorderLeft(); LCss::BorderDef BRight = BorderRight(); LCss::Len PLeft = PaddingLeft(); LCss::Len PRight = PaddingRight(); MarginPx = (int)(PLeft.ToPx() + PRight.ToPx() + BLeft.ToPx()); if (Table->BorderCollapse() == LCss::CollapseCollapse) MarginPx += BRight.ToPx(); } break; } case TAG_TABLE: { Len w = Width(); if (w.IsValid() && !w.IsDynamic()) { // Fixed width table... int CellSpacing = BorderSpacing().ToPx(Min, GetFont()); int Px = ((int)w.Value) + (CellSpacing << 1); Min = MAX(Min, Px); Max = MAX(Max, Px); return true; } else { LPoint s; LHtmlTableLayout c(this); c.GetSize(s.x, s.y); // Auto layout table LArray ColMin, ColMax; for (int y=0; yGetWidthMetrics(Table, a, b)) { ColMin[x] = MAX(ColMin[x], a); ColMax[x] = MAX(ColMax[x], b); } x += t->Cell->Span.x; } else break; } } int MinSum = 0, MaxSum = 0; for (int i=0; iGetWidthMetrics(Table, Min, TagMax); LineWidth += TagMax; if (c->TagId == TAG_BR || c->TagId == TAG_LI) { Max = MAX(Max, LineWidth); LineWidth = 0; } } Max = MAX(Max, LineWidth); Min += MarginPx; Max += MarginPx; return Status; } static void DistributeSize(LArray &a, int Start, int Span, int Size, int Border) { // Calculate the current size of the cells int Cur = -Border; for (int i=0; i T Sum(LArray &a) { T s = 0; for (unsigned i=0; iCells) { #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT if (Debug) { //int asd=0; } #endif Cell->Cells = new LHtmlTableLayout(this); #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT if (Cell->Cells && Debug) Cell->Cells->Dump(); #endif } if (Cell->Cells) Cell->Cells->LayoutTable(f, Depth); } void LHtmlTableLayout::AllocatePx(int StartCol, int Cols, int MinPx, bool HasToFillAllAvailable) { // Get the existing total size and size of the column set int CurrentTotalX = GetTotalX(); int CurrentSpanX = GetTotalX(StartCol, Cols); int MaxAdditionalPx = AvailableX - CurrentTotalX; if (MaxAdditionalPx <= 0) return; // Calculate the maximum space we have for this column set int AvailPx = (CurrentSpanX + MaxAdditionalPx) - BorderX1 - BorderX2; // Allocate any remaining space... int RemainingPx = MaxAdditionalPx; LArray Growable, NonGrowable, SizeInherit; int GrowablePx = 0; for (int x=StartCol; x 0) { GrowablePx += DiffPx; Growable.Add(x); } else if (MinCol[x] > 0) { NonGrowable.Add(x); } else if (MinCol[x] == 0 && CurrentSpanX < AvailPx) { // Growable.Add(x); } if (SizeCol[x].Type == LCss::LenInherit) SizeInherit.Add(x); } if (GrowablePx < RemainingPx && HasToFillAllAvailable) { if (Growable.Length() == 0) { // Add any suitable non-growable columns as well for (unsigned i=0; i MinCol[Largest]) Largest = i; } Growable.Add(Largest); } } if (Growable.Length()) { // Some growable columns... int Added = 0; // Reasonably increase the size of the columns... for (unsigned i=0; i 0) { AddPx = DiffPx; } else if (DiffPx > 0) { double Ratio = (double)DiffPx / GrowablePx; AddPx = (int) (Ratio * RemainingPx); } else { AddPx = RemainingPx / (int)Growable.Length(); } LAssert(AddPx >= 0); MinCol[x] += AddPx; LAssert(MinCol[x] >= 0); Added += AddPx; } if (Added < RemainingPx && HasToFillAllAvailable) { // Still more to add, so if (SizeInherit.Length()) { Growable = SizeInherit; } else { int Largest = -1; for (unsigned i=0; i MinCol[Largest]) Largest = x; } Growable.Length(1); Growable[0] = Largest; } int AddPx = (RemainingPx - Added) / (int)Growable.Length(); for (unsigned i=0; i= 0); } else { MinCol[x] += AddPx; LAssert(MinCol[x] >= 0); Added += AddPx; } } } } } struct ColInfo { int Large; int Growable; int Idx; int Px; }; int ColInfoCmp(ColInfo *a, ColInfo *b) { int LDiff = b->Large - a->Large; int LGrow = b->Growable - a->Growable; int LSize = b->Px - a->Px; return LDiff + LGrow + LSize; } void LHtmlTableLayout::DeallocatePx(int StartCol, int Cols, int MaxPx) { int TotalPx = GetTotalX(StartCol, Cols); if (TotalPx <= MaxPx || MaxPx == 0) return; int TrimPx = TotalPx - MaxPx; LArray Inf; int HalfMax = MaxPx >> 1; unsigned Interesting = 0; int InterestingPx = 0; for (int x=StartCol; x HalfMax; ci.Growable = MinCol[x] < MaxCol[x]; if (ci.Large || ci.Growable) { Interesting++; InterestingPx += ci.Px; } } Inf.Sort(ColInfoCmp); if (InterestingPx > 0) { for (unsigned i=0; i= 0); } else break; } } } int LHtmlTableLayout::GetTotalX(int StartCol, int Cols) { if (Cols < 0) Cols = s.x; int TotalX = BorderX1 + BorderX2 + CellSpacing; for (int x=StartCol; xZeroTableElements(); MinCol.Length(0); MaxCol.Length(0); MaxRow.Length(0); SizeCol.Length(0); LCss::Len BdrSpacing = Table->BorderSpacing(); CellSpacing = BdrSpacing.IsValid() ? (int)BdrSpacing.Value : 0; // Resolve total table width. TableWidth = Table->Width(); if (TableWidth.IsValid()) AvailableX = f->ResolveX(TableWidth, Table, false); else AvailableX = f->X(); LCss::Len MaxWidth = Table->MaxWidth(); if (MaxWidth.IsValid()) { int Px = f->ResolveX(MaxWidth, Table, false); if (Px < AvailableX) AvailableX = Px; } TableBorder = f->ResolveBorder(Table, Table); if (Table->BorderCollapse() != LCss::CollapseCollapse) TablePadding = f->ResolvePadding(Table, Table); else TablePadding.ZOff(0, 0); BorderX1 = TableBorder.x1 + TablePadding.x1; BorderX2 = TableBorder.x2 + TablePadding.x2; #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT if (Table->Debug) LgiTrace("AvailableX=%i, BorderX1=%i, BorderX2=%i\n", AvailableX, BorderX1, BorderX2); #endif #ifdef _DEBUG if (Table->Debug) { printf("Table Debug\n"); } #endif // Size detection pass int y; for (y=0; yGetFont(); t->Cell->BorderPx = f->ResolveBorder(t, t); t->Cell->PaddingPx = f->ResolvePadding(t, t); if (t->Cell->Pos.x == x && t->Cell->Pos.y == y) { LCss::DisplayType Disp = t->Display(); if (Disp == LCss::DispNone) continue; LCss::Len Content = t->Width(); if (Content.IsValid() && t->Cell->Span.x == 1) { if (SizeCol[x].IsValid()) { int OldPx = f->ResolveX(SizeCol[x], t, false); int NewPx = f->ResolveX(Content, t, false); if (NewPx > OldPx) { SizeCol[x] = Content; } } else { SizeCol[x] = Content; } } if (!t->GetWidthMetrics(Table, t->Cell->MinContent, t->Cell->MaxContent)) { t->Cell->MinContent = 16; t->Cell->MaxContent = 16; } #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT if (Table->Debug) LgiTrace("Content[%i,%i] MIN=%i MAX=%i\n", x, y, t->Cell->MinContent, t->Cell->MaxContent); #endif if (t->Cell->Span.x == 1) { int BoxPx = t->Cell->BorderPx.x1 + t->Cell->BorderPx.x2 + t->Cell->PaddingPx.x1 + t->Cell->PaddingPx.x2; MinCol[x] = MAX(MinCol[x], t->Cell->MinContent + BoxPx); LAssert(MinCol[x] >= 0); MaxCol[x] = MAX(MaxCol[x], t->Cell->MaxContent + BoxPx); } } x += t->Cell->Span.x; } else break; } } // How much space used so far? int TotalX = GetTotalX(); if (TotalX > AvailableX) { // FIXME: // Off -> 'cisra-cqs.html' renders correctly. // On -> 'cisra_outage.html', 'steam1.html' renders correctly. #if 1 DeallocatePx(0, (int)MinCol.Length(), AvailableX); TotalX = GetTotalX(); #endif } #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT #define DumpCols(msg) \ if (Table->Debug) \ { \ LgiTrace("%s Ln%i - TotalX=%i AvailableX=%i\n", msg, __LINE__, TotalX, AvailableX); \ for (unsigned i=0; iDebug) { printf("TableDebug\n"); } #endif // Process spanned cells for (y=0; yCell->Pos.x == x && t->Cell->Pos.y == y) { if (t->Cell->Span.x > 1 || t->Cell->Span.y > 1) { int i; int ColMin = -CellSpacing; int ColMax = -CellSpacing; for (i=0; iCell->Span.x; i++) { ColMin += MinCol[x + i] + CellSpacing; ColMax += MaxCol[x + i] + CellSpacing; } LCss::Len Width = t->Width(); if (Width.IsValid()) { int Px = f->ResolveX(Width, t, false); t->Cell->MinContent = MAX(t->Cell->MinContent, Px); t->Cell->MaxContent = MAX(t->Cell->MaxContent, Px); } #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT if (Table->Debug) LgiTrace("Content[%i,%i] MIN=%i MAX=%i\n", x, y, t->Cell->MinContent, t->Cell->MaxContent); #endif if (t->Cell->MinContent > ColMin) AllocatePx(t->Cell->Pos.x, t->Cell->Span.x, t->Cell->MinContent, false); if (t->Cell->MaxContent > ColMax) DistributeSize(MaxCol, t->Cell->Pos.x, t->Cell->Span.x, t->Cell->MaxContent, CellSpacing); } x += t->Cell->Span.x; } else break; } } TotalX = GetTotalX(); DumpCols("AfterSpannedCells"); // Sometimes the web page specifies too many percentages: // Scale them all. float PercentSum = 0.0f; for (int i=0; i 100.0) { float Ratio = PercentSum / 100.0f; for (int i=0; iResolveX(w, Table, false); if (w.Type == LCss::LenPercent) { MaxCol[x] = Px; } else if (Px > MinCol[x]) { int RemainingPx = AvailableX - TotalX; int AddPx = Px - MinCol[x]; AddPx = MIN(RemainingPx, AddPx); TotalX += AddPx; MinCol[x] += AddPx; LAssert(MinCol[x] >= 0); } } } } TotalX = GetTotalX(); DumpCols("AfterCssNonPercentageSizes"); if (TotalX > AvailableX) { #if !ALLOW_TABLE_GROWTH // Deallocate space if overused // Take some from the largest column int Largest = 0; for (int i=0; i MinCol[Largest]) { Largest = i; } } int Take = TotalX - AvailableX; if (Take < MinCol[Largest]) { MinCol[Largest] = MinCol[Largest] - Take; LAssert(MinCol[Largest] >= 0); TotalX -= Take; } DumpCols("AfterSpaceDealloc"); #endif } else if (TotalX < AvailableX) { AllocatePx(0, s.x, AvailableX, TableWidth.IsValid()); DumpCols("AfterRemainingAlloc"); } // Layout cell horizontally and then flow the contents to get // the height of all the cells LArray RowPad; MaxRow.Length(s.y); for (y=0; yCell->Pos.x == x && t->Cell->Pos.y == y) { t->Pos.x = XPos; t->Size.x = -CellSpacing; XPos -= CellSpacing; RowPad[y].y1 = MAX(RowPad[y].y1, t->Cell->BorderPx.y1 + t->Cell->PaddingPx.y1); RowPad[y].y2 = MAX(RowPad[y].y2, t->Cell->BorderPx.y2 + t->Cell->PaddingPx.y2); LRect Box(0, 0, -CellSpacing, 0); for (int i=0; iCell->Span.x; i++) { int ColSize = MinCol[x + i] + CellSpacing; LAssert(ColSize >= 0); if (ColSize < 0) break; t->Size.x += ColSize; XPos += ColSize; Box.x2 += ColSize; } LCss::Len Ht = t->Height(); LFlowRegion r(Table->Html, Box, true); t->OnFlow(&r, Depth+1); if (r.MAX.y > r.y2) { t->Size.y = MAX(r.MAX.y, t->Size.y); } if (Ht.IsValid() && Ht.Type != LCss::LenPercent) { int h = f->ResolveY(Ht, t, false); t->Size.y = MAX(h, t->Size.y); DistributeSize(MaxRow, y, t->Cell->Span.y, t->Size.y, CellSpacing); } } x += t->Cell->Span.x; } } #if defined(_DEBUG) DEBUG_LOG("%s:%i - AfterCellFlow\n", _FL); for (unsigned i=0; iCell->Pos.x == x && t->Cell->Pos.y == y) { LCss::Len Ht = t->Height(); if (!(Ht.IsValid() && Ht.Type != LCss::LenPercent)) { DistributeSize(MaxRow, y, t->Cell->Span.y, t->Size.y, CellSpacing); } } x += t->Cell->Span.x; } else break; } } // Cell positioning int Cx = BorderX1 + CellSpacing; int Cy = TableBorder.y1 + TablePadding.y1 + CellSpacing; for (y=0; yParent); if (Row && Row->TagId == TAG_TR) { t = new LTag(Table->Html, Row); if (t) { t->TagId = TAG_TD; t->Tag.Reset(NewStr("td")); t->Info = Table->Html->GetTagInfo(t->Tag); if ((t->Cell = new LTag::TblCell)) { t->Cell->Pos.x = x; t->Cell->Pos.y = y; t->Cell->Span.x = 1; t->Cell->Span.y = 1; } t->BackgroundColor(LCss::ColorDef(LCss::ColorRgb, DefaultMissingCellColour)); Set(Table); } else break; } else break; } if (t) { if (t->Cell->Pos.x == x && t->Cell->Pos.y == y) { int RowPadOffset = RowPad[y].y1 - t->Cell->BorderPx.y1 - t->Cell->PaddingPx.y1; t->Pos.x = Cx; t->Pos.y = Cy + RowPadOffset; t->Size.x = -CellSpacing; for (int i=0; iCell->Span.x; i++) { int w = MinCol[x + i] + CellSpacing; t->Size.x += w; Cx += w; } t->Size.y = -CellSpacing; for (int n=0; nCell->Span.y; n++) { t->Size.y += MaxRow[y+n] + CellSpacing; } Table->Size.x = MAX(Cx + BorderX2, Table->Size.x); #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT if (Table->Debug) { LgiTrace("cell(%i,%i) = pos(%i,%i)+size(%i,%i)\n", t->Cell->Pos.x, t->Cell->Pos.y, t->Pos.x, t->Pos.y, t->Size.x, t->Size.y); } #endif } else { Cx += t->Size.x + CellSpacing; } x += t->Cell->Span.x; } else break; Prev = t; } Cx = BorderX1 + CellSpacing; Cy += MaxRow[y] + CellSpacing; } switch (Table->Cell->XAlign ? Table->Cell->XAlign : ToTag(Table->Parent)->GetAlign(true)) { case LCss::AlignCenter: { int fx = f->X(); int Ox = (fx-Table->Size.x) >> 1; Table->Pos.x = f->x1 + MAX(Ox, 0); DEBUG_LOG("%s:%i - AlignCenter fx=%i ox=%i pos.x=%i size.x=%i\n", _FL, fx, Ox, Table->Pos.x, Table->Size.x); break; } case LCss::AlignRight: { Table->Pos.x = f->x2 - Table->Size.x; DEBUG_LOG("%s:%i - AlignRight f->x2=%i size.x=%i pos.x=%i\n", _FL, f->x2, Table->Size.x, Table->Pos.x); break; } default: { Table->Pos.x = f->x1; DEBUG_LOG("%s:%i - AlignLeft f->x1=%i size.x=%i pos.x=%i\n", _FL, f->x2, Table->Size.x, Table->Pos.x); break; } } Table->Pos.y = f->y1; Table->Size.y = Cy + TablePadding.y2 + TableBorder.y2; } LRect LTag::ChildBounds() { LRect b(0, 0, -1, -1); for (unsigned i=0; iGetRect(); b.Union(&c); } else { b = t->GetRect(); } } return b; } LPoint LTag::AbsolutePos() { LPoint p; for (LTag *t=this; t; t=ToTag(t->Parent)) { p += t->Pos; } return p; } void LTag::SetSize(LPoint &s) { Size = s; } LHtmlArea::~LHtmlArea() { DeleteObjects(); } LRect LHtmlArea::Bounds() { LRect n(0, 0, -1, -1); for (unsigned i=0; iLength(); i++) { LRect *r = (*c)[i]; if (!Top || (r && (r->y1 < Top->y1))) { Top = r; } } return Top; } void LHtmlArea::FlowText(LTag *Tag, LFlowRegion *Flow, LFont *Font, int LineHeight, char16 *Text, LCss::LengthType Align) { if (!Flow || !Text || !Font) return; SetFixedLength(false); char16 *Start = Text; size_t FullLen = StrlenW(Text); #if 1 if (!Tag->Html->GetReadOnly() && !*Text) { // Insert a text rect for this tag, even though it's empty. // This allows the user to place the cursor on a blank line. LFlowRect *Tr = new LFlowRect; Tr->Tag = Tag; Tr->Text = Text; Tr->x1 = Flow->cx; Tr->x2 = Tr->x1 + 1; Tr->y1 = Flow->y1; Tr->y2 = Tr->y1 + Font->GetHeight(); LAssert(Tr->y2 >= Tr->y1); Flow->y2 = MAX(Flow->y2, Tr->y2+1); Flow->cx = Tr->x2 + 1; Add(Tr); Flow->Insert(Tr, Align); return; } #endif while (*Text) { LFlowRect *Tr = new LFlowRect; if (!Tr) break; Tr->Tag = Tag; Restart: Tr->x1 = Flow->cx; Tr->y1 = Flow->y1; #if 1 // I removed this at one stage but forget why. // Remove white space at start of line if not in edit mode.. if (Tag->Html->GetReadOnly() && Flow->x1 == Flow->cx && *Text == ' ') { Text++; if (!*Text) { DeleteObj(Tr); break; } } #endif Tr->Text = Text; LDisplayString ds(Font, Text, MIN(1024, FullLen - (Text-Start))); ssize_t Chars = ds.CharAt(Flow->X()); bool Wrap = false; if (Text[Chars]) { // Word wrap // Seek back to the nearest break opportunity ssize_t n = Chars; while (n > 0 && !StrchrW(WhiteW, Text[n])) n--; if (n == 0) { if (Flow->x1 == Flow->cx) { // Already started from the margin and it's too long to // fit across the entire page, just let it hang off the right edge. // Seek to the end of the word for (Tr->Len = Chars; Text[Tr->Len] && !StrchrW(WhiteW, Text[Tr->Len]); Tr->Len++) ; // Wrap... if (*Text == ' ') Text++; } else { // Not at the start of the margin Flow->FinishLine(); goto Restart; } } else { Tr->Len = n; LAssert(Tr->Len > 0); Wrap = true; } } else { // Fits.. Tr->Len = Chars; LAssert(Tr->Len > 0); } LDisplayString ds2(Font, Tr->Text, Tr->Len); Tr->x2 = ds2.X(); Tr->y2 = LineHeight > 0 ? LineHeight - 1 : 0; if (Wrap) { Flow->cx = Flow->x1; Flow->y1 += Tr->y2 + 1; Tr->x2 = Flow->x2 - Tag->RelX(); } else { Tr->x2 += Tr->x1 - 1; Flow->cx = Tr->x2 + 1; } Tr->y2 += Tr->y1; Flow->y2 = MAX(Flow->y2, Tr->y2 + 1); Add(Tr); Flow->Insert(Tr, Align); Text += Tr->Len; if (Wrap) { while (*Text == ' ') Text++; } Tag->Size.x = MAX(Tag->Size.x, Tr->x2 + 1); Tag->Size.y = MAX(Tag->Size.y, Tr->y2 + 1); Flow->MAX.x = MAX(Flow->MAX.x, Tr->x2); Flow->MAX.y = MAX(Flow->MAX.y, Tr->y2); if (Tr->Len == 0) break; } SetFixedLength(true); } char16 htoi(char16 c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10; LAssert(0); return 0; } bool LTag::Serialize(LXmlTag *t, bool Write) { LRect pos; if (Write) { // Obj -> Tag if (Tag) t->SetAttr("tag", Tag); pos.ZOff(Size.x, Size.y); pos.Offset(Pos.x, Pos.y); t->SetAttr("pos", pos.GetStr()); t->SetAttr("tagid", TagId); if (Txt) { LStringPipe p(256); for (char16 *c = Txt; *c; c++) { if (*c > ' ' && *c < 127 && !strchr("%<>\'\"", *c)) p.Print("%c", (char)*c); else p.Print("%%%.4x", *c); } LAutoString Tmp(p.NewStr()); t->SetContent(Tmp); } if (Props.Length()) { auto CssStyles = ToString(); LAssert(!strchr(CssStyles, '\"')); t->SetAttr("style", CssStyles); } if (Html->Cursor == this) { LAssert(Cursor >= 0); t->SetAttr("cursor", (int64)Cursor); } else LAssert(Cursor < 0); if (Html->Selection == this) { LAssert(Selection >= 0); t->SetAttr("selection", (int64)Selection); } else LAssert(Selection < 0); for (unsigned i=0; iInsertTag(child); if (!tag->Serialize(child, Write)) { return false; } } } else { // Tag -> Obj Tag.Reset(NewStr(t->GetAttr("tag"))); TagId = (HtmlTag) t->GetAsInt("tagid"); pos.SetStr(t->GetAttr("pos")); if (pos.Valid()) { Pos.x = pos.x1; Pos.y = pos.y1; Size.x = pos.x2; Size.y = pos.y2; } if (ValidStr(t->GetContent())) { LStringPipe p(256); char *c = t->GetContent(); SkipWhiteSpace(c); for (; *c && *c > ' '; c++) { char16 ch; if (*c == '%') { ch = 0; for (int i=0; i<4 && *c; i++) { ch <<= 4; ch |= htoi(*++c); } } else ch = *c; p.Write(&ch, sizeof(ch)); } Txt.Reset(p.NewStrW()); } const char *s = t->GetAttr("style"); if (s) Parse(s, ParseRelaxed); s = t->GetAttr("cursor"); if (s) { LAssert(Html->Cursor == NULL); Html->Cursor = this; Cursor = atoi(s); LAssert(Cursor >= 0); } s = t->GetAttr("selection"); if (s) { LAssert(Html->Selection == NULL); Html->Selection = this; Selection = atoi(s); LAssert(Selection >= 0); } #ifdef _DEBUG s = t->GetAttr("debug"); if (s && atoi(s) != 0) Debug = true; #endif for (int i=0; iChildren.Length(); i++) { LXmlTag *child = t->Children[i]; if (child->IsTag("e")) { LTag *tag = new LTag(Html, NULL); if (!tag) { LAssert(0); return false; } if (!tag->Serialize(child, Write)) { return false; } Attach(tag); } } } return true; } /* /// This method centers the text in the area given to the tag. Used for inline block elements. void LTag::CenterText() { if (!Parent) return; // Find the size of the text elements. int ContentPx = 0; for (unsigned i=0; iX(); } LFont *f = GetFont(); int ParentPx = ToTag(Parent)->Size.x; int AvailPx = Size.x; // Remove the border and padding from the content area AvailPx -= BorderLeft().ToPx(ParentPx, f); AvailPx -= BorderRight().ToPx(ParentPx, f); AvailPx -= PaddingLeft().ToPx(ParentPx, f); AvailPx -= PaddingRight().ToPx(ParentPx, f); if (AvailPx > ContentPx) { // Now offset all the regions to the right int OffPx = (AvailPx - ContentPx) >> 1; for (unsigned i=0; iOffset(OffPx, 0); } } } */ void LTag::OnFlow(LFlowRegion *Flow, uint16 Depth) { if (Depth >= MAX_RECURSION_DEPTH) return; DisplayType Disp = Display(); if (Disp == DispNone) return; LFont *f = GetFont(); LFlowRegion Local(*Flow); bool Restart = true; int BlockFlowWidth = 0; const char *ImgAltText = NULL; Size.x = 0; Size.y = 0; LCssTools Tools(this, f); LRect rc(Flow->X(), Html->Y()); PadPx = Tools.GetPadding(rc); if (TipId) { Html->Tip.DeleteTip(TipId); TipId = 0; } switch (TagId) { default: break; case TAG_BODY: { Flow->InBody++; break; } case TAG_IFRAME: { LFlowRegion Temp = *Flow; Flow->EndBlock(); Flow->Indent(this, MarginLeft(), MarginTop(), MarginRight(), MarginBottom(), true); // Flow children for (unsigned i=0; iOnFlow(&Temp, Depth + 1); if (TagId == TAG_TR) { Temp.x2 -= MIN(t->Size.x, Temp.X()); } } Flow->Outdent(this, MarginLeft(), MarginTop(), MarginRight(), MarginBottom(), true); BoundParents(); return; break; } case TAG_TR: { Size.x = Flow->X(); break; } case TAG_IMG: { Size.x = Size.y = 0; LCss::Len w = Width(); LCss::Len h = Height(); // LCss::Len MinX = MinWidth(); // LCss::Len MaxX = MaxWidth(); LCss::Len MinY = MinHeight(); LCss::Len MaxY = MaxHeight(); LAutoPtr a; int ImgX, ImgY; if (Image) { ImgX = Image->X(); ImgY = Image->Y(); } else if (Get("alt", ImgAltText) && ValidStr(ImgAltText)) { LDisplayString a(f, ImgAltText); ImgX = a.X() + 4; ImgY = a.Y() + 4; } else { ImgX = DefaultImgSize; ImgY = DefaultImgSize; } double AspectRatio = ImgY != 0 ? (double)ImgX / ImgY : 1.0; bool XLimit = false, YLimit = false; double Scale = 1.0; if (w.IsValid() && w.Type != LenAuto) { Size.x = Flow->ResolveX(w, this, false); XLimit = true; } else { int Fx = Flow->x2 - Flow->x1 + 1; if (ImgX > Fx) { Size.x = Fx; // * 0.8; if (Image) Scale = (double) Fx / ImgX; } else { Size.x = ImgX; } } XLimit |= Flow->LimitX(Size.x, MinWidth(), MaxWidth(), f); if (h.IsValid() && h.Type != LenAuto) { Size.y = Flow->ResolveY(h, this, false); YLimit = true; } else { Size.y = (int) (ImgY * Scale); } YLimit |= Flow->LimitY(Size.y, MinHeight(), MaxHeight(), f); if ( (XLimit ^ YLimit) && Image ) { if (XLimit) { Size.y = (int) ceil((double)Size.x / AspectRatio); } else { Size.x = (int) ceil((double)Size.y * AspectRatio); } } if (MinY.IsValid()) { int Px = Flow->ResolveY(MinY, this, false); if (Size.y < Px) Size.y = Px; } if (MaxY.IsValid()) { int Px = Flow->ResolveY(MaxY, this, false); if (Size.y > Px) Size.y = Px; } if (Disp == DispInline || Disp == DispInlineBlock) { Restart = false; if (Flow->cx > Flow->x1 && Size.x > Flow->X()) { Flow->FinishLine(); } Pos.y = Flow->y1; Flow->y2 = MAX(Flow->y1, Pos.y + Size.y - 1); LCss::LengthType a = GetAlign(true); switch (a) { case AlignCenter: { int Fx = Flow->x2 - Flow->x1; Pos.x = Flow->x1 + ((Fx - Size.x) / 2); break; } case AlignRight: { Pos.x = Flow->x2 - Size.x; break; } default: { Pos.x = Flow->cx; break; } } } break; } case TAG_HR: { Flow->FinishLine(); Pos.x = Flow->x1; Pos.y = Flow->y1 + 7; Size.x = Flow->X(); Size.y = 2; Flow->cx ++; Flow->y2 += 16; Flow->FinishLine(); return; break; } case TAG_TABLE: { Flow->EndBlock(); LCss::Len left = GetCssLen(MarginLeft, Margin); LCss::Len top = GetCssLen(MarginTop, Margin); LCss::Len right = GetCssLen(MarginRight, Margin); LCss::Len bottom = GetCssLen(MarginBottom, Margin); Flow->Indent(this, left, top, right, bottom, true); LayoutTable(Flow, Depth + 1); Flow->y1 += Size.y; Flow->y2 = Flow->y1; Flow->cx = Flow->x1; Flow->my = 0; Flow->MAX.y = MAX(Flow->MAX.y, Flow->y2); Flow->Outdent(this, left, top, right, bottom, true); BoundParents(); return; } } if (Disp == DispBlock || Disp == DispInlineBlock) { // This is a block level element, so end the previous non-block elements if (Disp == DispBlock) Flow->EndBlock(); #ifdef _DEBUG if (Debug) LgiTrace("Before %s\n", Flow->ToString().Get()); #endif BlockFlowWidth = Flow->X(); // Indent the margin... LCss::Len left = GetCssLen(MarginLeft, Margin); LCss::Len top = GetCssLen(MarginTop, Margin); LCss::Len right = GetCssLen(MarginRight, Margin); LCss::Len bottom = GetCssLen(MarginBottom, Margin); Flow->Indent(this, left, top, right, bottom, true); // Set the width if any LCss::Len Wid = Width(); if (!IsTableCell(TagId) && Wid.IsValid()) Size.x = Flow->ResolveX(Wid, this, false); else if (TagId != TAG_IMG) { if (Disp == DispInlineBlock) // Flow->Inline) Size.x = 0; // block inside inline-block default to fit the content else Size.x = Flow->X(); } else if (Disp == DispInlineBlock) Size.x = 0; if (MaxWidth().IsValid()) { int Px = Flow->ResolveX(MaxWidth(), this, false); if (Size.x > Px) Size.x = Px; } if (MinWidth().IsValid()) { int Px = Flow->ResolveX(MinWidth(), this, false); if (Size.x < Px) Size.x = Px; } Pos.x = Disp == DispInlineBlock ? Flow->cx : Flow->x1; Pos.y = Flow->y1; Flow->y1 -= Pos.y; Flow->y2 -= Pos.y; if (Disp == DispBlock) { Flow->x1 -= Pos.x; Flow->x2 = Flow->x1 + Size.x; Flow->cx -= Pos.x; Flow->Indent(this, LCss::BorderLeft(), LCss::BorderTop(), LCss::BorderRight(), LCss::BorderBottom(), false); Flow->Indent(PadPx, false); } else { Flow->x2 = Flow->X(); Flow->x1 = Flow->ResolveX(BorderLeft(), this, true) + Flow->ResolveX(PaddingLeft(), this, true); Flow->cx = Flow->x1; Flow->y1 += Flow->ResolveY(BorderTop(), this, true) + Flow->ResolveY(PaddingTop(), this, true); Flow->y2 = Flow->y1; if (!IsTableTag()) Flow->Inline++; } } else { Flow->Indent(PadPx, false); } if (f) { // Clear the previous text layout... TextPos.DeleteObjects(); switch (TagId) { default: break; case TAG_LI: { // Insert the list marker if (!PreText()) { LCss::ListStyleTypes s = Parent->ListStyleType(); if (s == ListInherit) { if (Parent->TagId == TAG_OL) s = ListDecimal; else if (Parent->TagId == TAG_UL) s = ListDisc; } switch (s) { default: break; case ListDecimal: { ssize_t Index = Parent->Children.IndexOf(this); char Txt[32]; sprintf_s(Txt, sizeof(Txt), "%i. ", (int)(Index + 1)); PreText(Utf8ToWide(Txt)); break; } case ListDisc: { PreText(NewStrW(LHtmlListItem)); break; } } } if (PreText()) TextPos.FlowText(this, Flow, f, f->GetHeight(), PreText(), AlignLeft); break; } case TAG_IMG: { if (Disp == DispBlock) { Flow->cx += Size.x; Flow->y2 += Size.y; } break; } } if (Text() && Flow->InBody) { // Setup the line height cache if (LineHeightCache < 0) { LCss::Len LineHt; LFont *LineFnt = GetFont(); for (LTag *t = this; t && !LineHt.IsValid(); t = ToTag(t->Parent)) { LineHt = t->LineHeight(); if (t->TagId == TAG_TABLE) break; } if (LineFnt) { int FontPx = LineFnt->GetHeight(); if (!LineHt.IsValid() || LineHt.Type == LCss::LenAuto || LineHt.Type == LCss::LenNormal) { LineHeightCache = FontPx; // LgiTrace("LineHeight FontPx=%i Px=%i Auto\n", FontPx, LineHeightCache); } else if (LineHt.Type == LCss::LenPx) { auto Scale = Html->GetDpiScale().y; LineHt.Value *= (float)Scale; LineHeightCache = LineHt.ToPx(FontPx, f); // LgiTrace("LineHeight FontPx=%i Px=%i (Scale=%f)\n", FontPx, LineHeightCache, Scale); } else { LineHeightCache = LineHt.ToPx(FontPx, f); // LgiTrace("LineHeight FontPx=%i Px=%i ToPx\n", FontPx, LineHeightCache); } } } // Flow in the rest of the text... char16 *Txt = Text(); LCss::LengthType Align = GetAlign(true); TextPos.FlowText(this, Flow, f, LineHeightCache, Txt, Align); #ifdef _DEBUG if (Debug) LgiTrace("%s:%i - %p.size=%p\n", _FL, this, &Size.x); #endif } } // Flow children PostFlowAlign.Length(0); for (unsigned i=0; iPosition()) { case PosStatic: case PosAbsolute: case PosFixed: { LFlowRegion old = *Flow; t->OnFlow(Flow, Depth + 1); // Try and reset the flow to how it was before... Flow->x1 = old.x1; Flow->x2 = old.x2; Flow->cx = old.cx; Flow->y1 = old.y1; Flow->y2 = old.y2; Flow->MAX.x = MAX(Flow->MAX.x, old.MAX.x); Flow->MAX.y = MAX(Flow->MAX.y, old.MAX.y); break; } default: { t->OnFlow(Flow, Depth + 1); break; } } if (TagId == TAG_TR) { Flow->x2 -= MIN(t->Size.x, Flow->X()); } } LCss::LengthType XAlign = GetAlign(true); int FlowSz = Flow->Width(); // Align the children... for (auto &group: PostFlowAlign) { int MinX = FlowSz, MaxX = 0; for (auto &a: group) { MinX = MIN(MinX, a.t->Pos.x); MaxX = MAX(MaxX, a.t->Pos.x + a.t->Size.x - 1); } int TotalX = MaxX - MinX + 1; int FirstX = group.Length() ? group[0].t->Pos.x : 0; for (auto &a: group) { if (a.XAlign == LCss::AlignCenter) { int OffX = (Size.x - TotalX) >> 1; if (OffX > 0) { a.t->Pos.x += OffX; } } else if (a.XAlign == LCss::AlignRight) { int OffX = FlowSz - FirstX - TotalX; if (OffX > 0) { a.t->Pos.x += OffX; } } } } if (Disp == DispBlock || Disp == DispInlineBlock) { LCss::Len Ht = Height(); LCss::Len MaxHt = MaxHeight(); // I dunno, there should be a better way... :-( if (MarginLeft().Type == LenAuto && MarginRight().Type == LenAuto) { XAlign = LCss::AlignCenter; } bool AcceptHt = !IsTableCell(TagId) && Ht.Type != LenPercent; if (AcceptHt) { if (Ht.IsValid()) { int HtPx = Flow->ResolveY(Ht, this, false); if (HtPx > Flow->y2) Flow->y2 = HtPx; } if (MaxHt.IsValid()) { int MaxHtPx = Flow->ResolveY(MaxHt, this, false); if (MaxHtPx < Flow->y2) { Flow->y2 = MaxHtPx; Flow->MAX.y = MIN(Flow->y2, Flow->MAX.y); } } } if (Disp == DispBlock) { Flow->EndBlock(); int OldFlowSize = Flow->x2 - Flow->x1 + 1; Flow->Outdent(this, PaddingLeft(), PaddingTop(), PaddingRight(), PaddingBottom(), false); Flow->Outdent(this, LCss::BorderLeft(), LCss::BorderTop(), LCss::BorderRight(), LCss::BorderBottom(), false); Size.y = Flow->y2 > 0 ? Flow->y2 : 0; Flow->Outdent(this, MarginLeft(), MarginTop(), MarginRight(), MarginBottom(), true); int NewFlowSize = Flow->x2 - Flow->x1 + 1; int Diff = NewFlowSize - OldFlowSize; if (Diff) Flow->MAX.x += Diff; Flow->y1 = Flow->y2; Flow->x2 = Flow->x1 + BlockFlowWidth; } else { LCss::Len Wid = Width(); int WidPx = Wid.IsValid() ? Flow->ResolveX(Wid, this, true) : 0; Size.x = MAX(WidPx, Size.x); Size.x += Flow->ResolveX(PaddingRight(), this, true); Size.x += Flow->ResolveX(BorderRight(), this, true); int MarginR = Flow->ResolveX(MarginRight(), this, true); int MarginB = Flow->ResolveX(MarginBottom(), this, true); Flow->x1 = Local.x1 - Pos.x; Flow->cx = Local.cx + Size.x + MarginR - Pos.x; Flow->x2 = Local.x2 - Pos.x; if (Height().IsValid()) { Size.y = Flow->ResolveY(Height(), this, false); Flow->y2 = MAX(Flow->y1 + Size.y + MarginB - 1, Flow->y2); } else { Flow->y2 += Flow->ResolveX(PaddingBottom(), this, true); Flow->y2 += Flow->ResolveX(BorderBottom(), this, true); Size.y = Flow->y2; } Flow->y1 = Local.y1 - Pos.y; Flow->y2 = MAX(Local.y2, Flow->y1+Size.y-1); if (!IsTableTag()) Flow->Inline--; } // Can't do alignment here because pos is used to // restart the parents flow region... } else { Flow->Outdent(PadPx, false); switch (TagId) { default: break; case TAG_SELECT: case TAG_INPUT: { if (Html->InThread() && Ctrl) { LRect r = Ctrl->GetPos(); if (Width().IsValid()) Size.x = Flow->ResolveX(Width(), this, false); else Size.x = r.X(); if (Height().IsValid()) Size.y = Flow->ResolveY(Height(), this, false); else Size.y = r.Y(); if (Html->IsAttached() && !Ctrl->IsAttached()) Ctrl->Attach(Html); } Flow->cx += Size.x; Flow->y2 = MAX(Flow->y2, Flow->y1 + Size.y - 1); break; } case TAG_IMG: { Flow->cx += Size.x; Flow->y2 = MAX(Flow->y2, Flow->y1 + Size.y - 1); break; } case TAG_BR: { int OldFlowY2 = Flow->y2; Flow->FinishLine(); Size.y = Flow->y2 - OldFlowY2; Flow->y2 = MAX(Flow->y2, Flow->y1 + Size.y - 1); break; } case TAG_CENTER: { int Px = Flow->X(); for (auto e: Children) { LTag *t = ToTag(e); if (t && t->IsBlock() && t->Size.x < Px) { t->Pos.x = (Px - t->Size.x) >> 1; } } break; } } } BoundParents(); if (Restart) { Flow->x1 += Pos.x; Flow->x2 += Pos.x; Flow->cx += Pos.x; Flow->y1 += Pos.y; Flow->y2 += Pos.y; Flow->MAX.y = MAX(Flow->MAX.y, Flow->y2); } if (Disp == DispBlock || Disp == DispInlineBlock) { if (XAlign == LCss::AlignCenter || XAlign == LCss::AlignRight) { int Match = 0; auto parent = ToTag(Parent); for (auto &grp: parent->PostFlowAlign) { bool Overlaps = false; for (auto &a: grp) { if (a.Overlap(this)) { Overlaps = true; break; } } if (!Overlaps) Match++; } auto &grp = parent->PostFlowAlign[Match]; if (grp.Length() == 0) { grp.x1 = Flow->x1; grp.x2 = Flow->x2; } auto &pf = grp.New(); pf.Disp = Disp; pf.XAlign = XAlign; pf.t = this; } } if (TagId == TAG_BODY && Flow->InBody > 0) { Flow->InBody--; } } bool LTag::PeekTag(char *s, char *tag) { bool Status = false; if (s && tag) { if (*s == '<') { char *t = 0; Html->ParseName(++s, &t); if (t) { Status = _stricmp(t, tag) == 0; } DeleteArray(t); } } return Status; } LTag *LTag::GetTable() { LTag *t = 0; for (t=ToTag(Parent); t && t->TagId != TAG_TABLE; t = ToTag(t->Parent)) ; return t; } void LTag::BoundParents() { if (!Parent) return; LTag *np; for (LTag *n=this; n; n = np) { np = ToTag(n->Parent); if (!np || np->TagId == TAG_IFRAME) break; np->Size.x = MAX(np->Size.x, n->Pos.x + n->Size.x); np->Size.y = MAX(np->Size.y, n->Pos.y + n->Size.y); } } struct DrawBorder { LSurface *pDC; uint32_t LineStyle; uint32_t LineReset; uint32_t OldStyle; DrawBorder(LSurface *pdc, LCss::BorderDef &d) { LineStyle = 0xffffffff; LineReset = 0x80000000; if (d.Style == LCss::BorderDotted) { switch ((int)d.Value) { case 2: { LineStyle = 0xcccccccc; break; } case 3: { LineStyle = 0xe38e38; LineReset = 0x800000; break; } case 4: { LineStyle = 0xf0f0f0f0; break; } case 5: { LineStyle = 0xf83e0; LineReset = 0x80000; break; } case 6: { LineStyle = 0xfc0fc0; LineReset = 0x800000; break; } case 7: { LineStyle = 0xfe03f80; LineReset = 0x8000000; break; } case 8: { LineStyle = 0xff00ff00; break; } case 9: { LineStyle = 0x3fe00; LineReset = 0x20000; break; } default: { LineStyle = 0xaaaaaaaa; break; } } } pDC = pdc; OldStyle = pDC->LineStyle(); } ~DrawBorder() { pDC->LineStyle(OldStyle); } }; void LTag::GetInlineRegion(LRegion &rgn, int ox, int oy) { if (TagId == TAG_IMG) { LRect rc(0, 0, Size.x-1, Size.y-1); rc.Offset(ox + Pos.x, oy + Pos.y); rgn.Union(&rc); } else { for (unsigned i=0; iGetInlineRegion(rgn, ox + Pos.x, oy + Pos.y); } } class CornersImg : public LMemDC { public: int Px, Px2; CornersImg( float RadPx, LRect *BorderPx, LCss::BorderDef **defs, LColour &Back, bool DrawBackground) { Px = 0; Px2 = 0; //Radius.Type != LCss::LenInherit && if (RadPx > 0.0f) { Px = (int)ceil(RadPx); Px2 = Px << 1; if (Create(Px2, Px2, System32BitColourSpace)) { #if 1 Colour(0, 32); #else Colour(LColour(255, 0, 255)); #endif Rectangle(); LPointF ctr(Px, Px); LPointF LeftPt(0.0, Px); LPointF TopPt(Px, 0.0); LPointF RightPt(X(), Px); LPointF BottomPt(Px, Y()); int x_px[4] = {BorderPx->x1, BorderPx->x2, BorderPx->x2, BorderPx->x1}; int y_px[4] = {BorderPx->y1, BorderPx->y1, BorderPx->y2, BorderPx->y2}; LPointF *pts[4] = {&LeftPt, &TopPt, &RightPt, &BottomPt}; // Draw border parts.. for (int i=0; i<4; i++) { int k = (i + 1) % 4; // Setup the stops LBlendStop stops[2] = { {0.0, 0}, {1.0, 0} }; uint32_t iColour = defs[i]->Color.IsValid() ? defs[i]->Color.Rgb32 : Back.c32(); uint32_t kColour = defs[k]->Color.IsValid() ? defs[k]->Color.Rgb32 : Back.c32(); if (defs[i]->IsValid() && defs[k]->IsValid()) { stops[0].c32 = iColour; stops[1].c32 = kColour; } else if (defs[i]->IsValid()) { stops[0].c32 = stops[1].c32 = iColour; } else { stops[0].c32 = stops[1].c32 = kColour; } // Create a brush LLinearBlendBrush br ( *pts[i], *pts[k], 2, stops ); // Setup the clip LRect clip( (int)MIN(pts[i]->x, pts[k]->x), (int)MIN(pts[i]->y, pts[k]->y), (int)MAX(pts[i]->x, pts[k]->x)-1, (int)MAX(pts[i]->y, pts[k]->y)-1); ClipRgn(&clip); // Draw the arc... LPath p; p.Circle(ctr, Px); if (defs[i]->IsValid() || defs[k]->IsValid()) p.Fill(this, br); // Fill the background p.Empty(); p.Ellipse(ctr, Px-x_px[i], Px-y_px[i]); if (DrawBackground) { LSolidBrush br(Back); p.Fill(this, br); } else { LEraseBrush br; p.Fill(this, br); } ClipRgn(NULL); } #ifdef MAC ConvertPreMulAlpha(true); #endif #if 0 static int count = 0; LString file; file.Printf("c:\\temp\\img-%i.bmp", ++count); GdcD->Save(file, Corners); #endif } } } }; void LTag::PaintBorderAndBackground(LSurface *pDC, LColour &Back, LRect *BorderPx) { LArray r; LRect BorderPxRc; bool DrawBackground = !Back.IsTransparent(); #ifdef _DEBUG if (Debug) { //int asd=0; } #endif if (!BorderPx) BorderPx = &BorderPxRc; BorderPx->ZOff(0, 0); // Get all the border info and work out the pixel sizes. LFont *f = GetFont(); #define DoEdge(coord, axis, name) \ BorderDef name = Border##name(); \ BorderPx->coord = name.Style != LCss::BorderNone ? name.ToPx(Size.axis, f) : 0; #define BorderValid(name) \ ((name).IsValid() && (name).Style != LCss::BorderNone) DoEdge(x1, x, Left); DoEdge(y1, y, Top); DoEdge(x2, x, Right); DoEdge(y2, y, Bottom); LCss::BorderDef *defs[4] = {&Left, &Top, &Right, &Bottom}; if (BorderValid(Left) || BorderValid(Right) || BorderValid(Top) || BorderValid(Bottom) || DrawBackground) { // Work out the rectangles switch (Display()) { case DispInlineBlock: case DispBlock: { r[0].ZOff(Size.x-1, Size.y-1); break; } case DispInline: { LRegion rgn; GetInlineRegion(rgn); if (BorderPx) { for (int i=0; ix1 -= BorderPx->x1 + PadPx.x1; r->y1 -= BorderPx->y1 + PadPx.y1; r->x2 += BorderPx->x2 + PadPx.x2; r->y2 += BorderPx->y2 + PadPx.y2; } } r.Length(rgn.Length()); auto p = r.AddressOf(); for (auto i = rgn.First(); i; i = rgn.Next()) *p++ = *i; break; } default: return; } // If we are drawing rounded corners, draw them into a memory context LAutoPtr Corners; int Px = 0, Px2 = 0; LCss::Len Radius = BorderRadius(); float RadPx = Radius.Type == LCss::LenPx ? Radius.Value : Radius.ToPx(Size.x, GetFont()); bool HasRadius = Radius.Type != LCss::LenInherit && RadPx > 0.0f; // Loop over the rectangles and draw everything int Op = pDC->Op(GDC_ALPHA); for (unsigned i=0; i rc.Y()) { Px = rc.Y() / 2; Px2 = Px << 1; } if (!Corners || Corners->Px2 != Px2) { Corners.Reset(new CornersImg((float)Px, BorderPx, defs, Back, DrawBackground)); } // top left LRect r(0, 0, Px-1, Px-1); pDC->Blt(rc.x1, rc.y1, Corners, &r); // top right r.Set(Px, 0, Corners->X()-1, Px-1); pDC->Blt(rc.x2-Px+1, rc.y1, Corners, &r); // bottom left r.Set(0, Px, Px-1, Corners->Y()-1); pDC->Blt(rc.x1, rc.y2-Px+1, Corners, &r); // bottom right r.Set(Px, Px, Corners->X()-1, Corners->Y()-1); pDC->Blt(rc.x2-Px+1, rc.y2-Px+1, Corners, &r); #if 1 pDC->Colour(Back); pDC->Rectangle(rc.x1+Px, rc.y1, rc.x2-Px, rc.y2); pDC->Rectangle(rc.x1, rc.y1+Px, rc.x1+Px-1, rc.y2-Px); pDC->Rectangle(rc.x2-Px+1, rc.y1+Px, rc.x2, rc.y2-Px); #else pDC->Colour(LColour(255, 0, 0, 0x80)); pDC->Rectangle(rc.x1+Px, rc.y1, rc.x2-Px, rc.y2); pDC->Colour(LColour(0, 255, 0, 0x80)); pDC->Rectangle(rc.x1, rc.y1+Px, rc.x1+Px-1, rc.y2-Px); pDC->Colour(LColour(0, 0, 255, 0x80)); pDC->Rectangle(rc.x2-Px+1, rc.y1+Px, rc.x2, rc.y2-Px); #endif } else if (DrawBackground) { pDC->Colour(Back); pDC->Rectangle(&rc); } LCss::BorderDef *b; if ((b = &Left) && BorderValid(*b)) { pDC->Colour(b->Color.Rgb32, 32); DrawBorder db(pDC, *b); for (int i=0; iValue; i++) { pDC->LineStyle(db.LineStyle, db.LineReset); pDC->Line(rc.x1 + i, rc.y1+Px, rc.x1+i, rc.y2-Px); } } if ((b = &Top) && BorderValid(*b)) { pDC->Colour(b->Color.Rgb32, 32); DrawBorder db(pDC, *b); for (int i=0; iValue; i++) { pDC->LineStyle(db.LineStyle, db.LineReset); pDC->Line(rc.x1+Px, rc.y1+i, rc.x2-Px, rc.y1+i); } } if ((b = &Right) && BorderValid(*b)) { pDC->Colour(b->Color.Rgb32, 32); DrawBorder db(pDC, *b); for (int i=0; iValue; i++) { pDC->LineStyle(db.LineStyle, db.LineReset); pDC->Line(rc.x2-i, rc.y1+Px, rc.x2-i, rc.y2-Px); } } if ((b = &Bottom) && BorderValid(*b)) { pDC->Colour(b->Color.Rgb32, 32); DrawBorder db(pDC, *b); for (int i=0; iValue; i++) { pDC->LineStyle(db.LineStyle, db.LineReset); pDC->Line(rc.x1+Px, rc.y2-i, rc.x2-Px, rc.y2-i); } } } pDC->Op(Op); } } static void FillRectWithImage(LSurface *pDC, LRect *r, LSurface *Image, LCss::RepeatType Repeat) { int Px = 0, Py = 0; int Old = pDC->Op(GDC_ALPHA); if (!Image) return; switch (Repeat) { default: case LCss::RepeatBoth: { for (int y=0; yY(); y += Image->Y()) { for (int x=0; xX(); x += Image->X()) { pDC->Blt(Px + x, Py + y, Image); } } break; } case LCss::RepeatX: { for (int x=0; xX(); x += Image->X()) { pDC->Blt(Px + x, Py, Image); } break; } case LCss::RepeatY: { for (int y=0; yY(); y += Image->Y()) { pDC->Blt(Px, Py + y, Image); } break; } case LCss::RepeatNone: { pDC->Blt(Px, Py, Image); break; } } pDC->Op(Old); } void LTag::OnPaint(LSurface *pDC, bool &InSelection, uint16 Depth) { if (Depth >= MAX_RECURSION_DEPTH || Display() == DispNone) return; if ( #ifdef _DEBUG !Html->_Debug && #endif LCurrentTime() - Html->PaintStart > Html->d->MaxPaintTime) { Html->d->MaxPaintTimeout = true; return; } int Px, Py; pDC->GetOrigin(Px, Py); #if 0 if (Debug) { Gtk::cairo_matrix_t mx; Gtk::cairo_get_matrix(pDC->Handle(), &mx); LPoint Offset; Html->WindowVirtualOffset(&Offset); LRect cli; pDC->GetClient(&cli); printf("\tTag paint mx=%g,%g off=%i,%i p=%i,%i Pos=%i,%i cli=%s\n", mx.x0, mx.y0, Offset.x, Offset.y, Px, Py, Pos.x, Pos.y, cli.GetStr()); } #endif switch (TagId) { case TAG_INPUT: case TAG_SELECT: { if (Ctrl) { int64 Sx = 0, Sy = 0; int64 LineY = GetFont()->GetHeight(); Html->GetScrollPos(Sx, Sy); Sx *= LineY; Sy *= LineY; LRect r(0, 0, Size.x-1, Size.y-1), Px; LColour back = _Colour(false); PaintBorderAndBackground(pDC, back, &Px); if (!dynamic_cast(Ctrl)) { r.x1 += Px.x1; r.y1 += Px.y1; r.x2 -= Px.x2; r.y2 -= Px.y2; } r.Offset(AbsX() - (int)Sx, AbsY() - (int)Sy); Ctrl->SetPos(r); } if (TagId == TAG_SELECT) return; break; } case TAG_BODY: { auto b = _Colour(false); if (!b.IsTransparent()) { pDC->Colour(b); pDC->Rectangle(Pos.x, Pos.y, Pos.x+Size.x, Pos.y+Size.y); } if (Image) { LRect r; r.ZOff(Size.x-1, Size.y-1); FillRectWithImage(pDC, &r, Image, BackgroundRepeat()); } break; } case TAG_HEAD: { // Nothing under here to draw. return; } case TAG_HR: { pDC->Colour(L_MED); pDC->Rectangle(0, 0, Size.x - 1, Size.y - 1); break; } case TAG_TR: case TAG_TBODY: case TAG_META: { // Draws nothing... break; } case TAG_IMG: { LRect Clip(0, 0, Size.x-1, Size.y-1); pDC->ClipRgn(&Clip); if (Image) { #if ENABLE_IMAGE_RESIZING if ( !ImageResized && ( Size.x != Image->X() || Size.y != Image->Y() ) ) { ImageResized = true; LColourSpace Cs = Image->GetColourSpace(); if (Cs == CsIndex8 && Image->AlphaDC()) Cs = System32BitColourSpace; LAutoPtr r(new LMemDC(Size.x, Size.y, Cs)); if (r) { if (Cs == CsIndex8) r->Palette(new LPalette(Image->Palette())); ResampleDC(r, Image); Image = r; } } #endif int Old = pDC->Op(GDC_ALPHA); pDC->Blt(0, 0, Image); pDC->Op(Old); } else if (Size.x > 1 && Size.y > 1) { LRect b(0, 0, Size.x-1, Size.y-1); LColour Fill(LColour(L_MED).Mix(LColour(L_LIGHT), 0.2f)); LColour Border(L_MED); // Border pDC->Colour(Border); pDC->Box(&b); b.Inset(1, 1); pDC->Box(&b); b.Inset(1, 1); pDC->Colour(Fill); pDC->Rectangle(&b); const char *Alt; LColour Red(LColour(255, 0, 0).Mix(Fill, 0.3f)); if (Get("alt", Alt) && ValidStr(Alt)) { LDisplayString Ds(Html->GetFont(), Alt); Html->GetFont()->Colour(Red, Fill); Ds.Draw(pDC, 2, 2, &b); } else if (Size.x >= 16 && Size.y >= 16) { // Red 'x' int Cx = b.x1 + (b.X()/2); int Cy = b.y1 + (b.Y()/2); LRect c(Cx-4, Cy-4, Cx+4, Cy+4); pDC->Colour(Red); pDC->Line(c.x1, c.y1, c.x2, c.y2); pDC->Line(c.x1, c.y2, c.x2, c.y1); pDC->Line(c.x1, c.y1 + 1, c.x2 - 1, c.y2); pDC->Line(c.x1 + 1, c.y1, c.x2, c.y2 - 1); pDC->Line(c.x1 + 1, c.y2, c.x2, c.y1 + 1); pDC->Line(c.x1, c.y2 - 1, c.x2 - 1, c.y1); } } pDC->ClipRgn(0); break; } default: { LColour fore = _Colour(true); LColour back = _Colour(false); if (Display() == DispBlock && Html->Environment) { LCss::ImageDef Img = BackgroundImage(); if (Img.Img) { LRect Clip(0, 0, Size.x-1, Size.y-1); pDC->ClipRgn(&Clip); FillRectWithImage(pDC, &Clip, Img.Img, BackgroundRepeat()); pDC->ClipRgn(NULL); back.Empty(); } } PaintBorderAndBackground(pDC, back, NULL); LFont *f = GetFont(); #if DEBUG_TEXT_AREA bool IsEditor = Html ? !Html->GetReadOnly() : false; #else bool IsEditor = false; #endif if (f && TextPos.Length()) { // This is the non-display part of the font bounding box int LeadingPx = (int)(f->Leading() + 0.5); // This is the displayable part of the font int FontPx = f->GetHeight() - LeadingPx; // This is the pixel height we're aiming to fill int EffectiveLineHt = LineHeightCache >= 0 ? MAX(FontPx, LineHeightCache) : FontPx; // This gets added to the y coord of each piece of text int LineHtOff = ((EffectiveLineHt - FontPx + 1) >> 1) - LeadingPx; #define FontColour(InSelection) \ f->Transparent(!InSelection && !IsEditor); \ if (InSelection) \ f->Colour(L_FOCUS_SEL_FORE, L_FOCUS_SEL_BACK); \ else \ { \ LColour bk(back.IsTransparent() ? LColour(L_WORKSPACE) : back); \ LColour fr(fore.IsTransparent() ? LColour(DefaultTextColour) : fore); \ if (IsEditor) \ bk = bk.Mix(LColour::Black, 0.05f); \ f->Colour(fr, bk); \ } if (Html->HasSelection() && (Selection >= 0 || Cursor >= 0) && Selection != Cursor) { ssize_t Min = -1; ssize_t Max = -1; ssize_t Base = GetTextStart(); if (Cursor >= 0 && Selection >= 0) { Min = MIN(Cursor, Selection) + Base; Max = MAX(Cursor, Selection) + Base; } else if (InSelection) { Max = MAX(Cursor, Selection) + Base; } else { Min = MAX(Cursor, Selection) + Base; } LRect CursorPos; CursorPos.ZOff(-1, -1); for (unsigned i=0; iText - Text(); ssize_t Done = 0; int x = Tr->x1; if (Tr->Len == 0) { // Is this a selection edge point? if (!InSelection && Min == 0) { InSelection = !InSelection; } else if (InSelection && Max == 0) { InSelection = !InSelection; } if (Cursor >= 0) { // Is this the cursor, then draw it and save it's position if (Cursor == Start + Done - Base) { Html->d->CursorPos.Set(x, Tr->y1 + LineHtOff, x + 1, Tr->y2 - LineHtOff); if (Html->d->CursorPos.x1 > Tr->x2) Html->d->CursorPos.Offset(Tr->x2 - Html->d->CursorPos.x1, 0); CursorPos = Html->d->CursorPos; Html->d->CursorPos.Offset(AbsX(), AbsY()); } } break; } while (Done < Tr->Len) { ssize_t c = Tr->Len - Done; FontColour(InSelection); // Is this a selection edge point? if ( !InSelection && Min - Start >= Done && Min - Start < Done + Tr->Len) { InSelection = !InSelection; c = Min - Start - Done; } else if ( InSelection && Max - Start >= Done && Max - Start <= Tr->Len) { InSelection = !InSelection; c = Max - Start - Done; } // Draw the text run LDisplayString ds(f, Tr->Text + Done, c); if (IsEditor) { LRect r(x, Tr->y1, x + ds.X() - 1, Tr->y2); ds.Draw(pDC, x, Tr->y1 + LineHtOff, &r); } else { ds.Draw(pDC, x, Tr->y1 + LineHtOff); } x += ds.X(); Done += c; // Is this is end of the tag? if (Tr->Len == Done) { // Is it also a selection edge? if ( !InSelection && Min - Start == Done) { InSelection = !InSelection; } else if ( InSelection && Max - Start == Done) { InSelection = !InSelection; } } if (Cursor >= 0) { // Is this the cursor, then draw it and save it's position if (Cursor == Start + Done - Base) { Html->d->CursorPos.Set(x, Tr->y1 + LineHtOff, x + 1, Tr->y2 - LineHtOff); if (Html->d->CursorPos.x1 > Tr->x2) Html->d->CursorPos.Offset(Tr->x2 - Html->d->CursorPos.x1, 0); CursorPos = Html->d->CursorPos; Html->d->CursorPos.Offset(AbsX(), AbsY()); } } } } if (Html->d->CursorVis && CursorPos.Valid()) { pDC->Colour(L_TEXT); pDC->Rectangle(&CursorPos); } } else if (Cursor >= 0) { FontColour(InSelection); ssize_t Base = GetTextStart(); for (unsigned i=0; iText - Text()) - Base; LAssert(Tr->y2 >= Tr->y1); LDisplayString ds(f, Tr->Text, Tr->Len); ds.Draw(pDC, Tr->x1, Tr->y1 + LineHtOff, IsEditor ? Tr : NULL); if ( ( Tr->Text == PreText() && !ValidStrW(Text()) ) || ( Cursor >= Pos && Cursor <= Pos + Tr->Len ) ) { ssize_t Off = Tr->Text == PreText() ? StrlenW(PreText()) : Cursor - Pos; pDC->Colour(L_TEXT); LRect c; if (Off) { LDisplayString ds(f, Tr->Text, Off); int x = ds.X(); if (x >= Tr->X()) x = Tr->X()-1; c.Set(Tr->x1 + x, Tr->y1, Tr->x1 + x + 1, Tr->y1 + f->GetHeight()); } else { c.Set(Tr->x1, Tr->y1, Tr->x1 + 1, Tr->y1 + f->GetHeight()); } Html->d->CursorPos = c; if (Html->d->CursorVis) pDC->Rectangle(&c); Html->d->CursorPos.Offset(AbsX(), AbsY()); } } } else { FontColour(InSelection); for (auto &Tr: TextPos) { LDisplayString ds(f, Tr->Text, Tr->Len); ds.Draw(pDC, Tr->x1, Tr->y1 + LineHtOff, IsEditor ? Tr : NULL); } } } break; } } #if DEBUG_TABLE_LAYOUT && 0 if (IsTableCell(TagId)) { LTag *Tbl = this; while (Tbl->TagId != TAG_TABLE && Tbl->Parent) Tbl = Tbl->Parent; if (Tbl && Tbl->TagId == TAG_TABLE && Tbl->Debug) { pDC->Colour(LColour(255, 0, 0)); pDC->Box(0, 0, Size.x-1, Size.y-1); } } #endif for (unsigned i=0; iSetOrigin(Px - t->Pos.x, Py - t->Pos.y); t->OnPaint(pDC, InSelection, Depth + 1); pDC->SetOrigin(Px, Py); } #if DEBUG_DRAW_TD if (TagId == TAG_TD) { LTag *Tbl = this; while (Tbl && Tbl->TagId != TAG_TABLE) Tbl = ToTag(Tbl->Parent); if (Tbl && Tbl->Debug) { int Ls = pDC->LineStyle(LSurface::LineDot); pDC->Colour(LColour::Blue); pDC->Box(0, 0, Size.x-1, Size.y-1); pDC->LineStyle(Ls); } } #endif } ////////////////////////////////////////////////////////////////////// LHtml::LHtml(int id, int x, int y, int cx, int cy, LDocumentEnv *e) : LDocView(e), ResObject(Res_Custom), LHtmlParser(NULL) { View = this; d = new LHtmlPrivate; SetReadOnly(true); ViewWidth = -1; SetId(id); LRect r(x, y, x+cx, y+cy); SetPos(r); Cursor = 0; Selection = 0; DocumentUid = 0; _New(); } LHtml::~LHtml() { _Delete(); DeleteObj(d); if (JobSem.Lock(_FL)) { JobSem.Jobs.DeleteObjects(); JobSem.Unlock(); } } void LHtml::_New() { d->StyleDirty = false; d->IsLoaded = false; d->Content.x = d->Content.y = 0; d->DeferredLoads = 0; Tag = 0; DocCharSet.Reset(); IsHtml = true; #ifdef DefaultFont LFont *Def = new LFont; if (Def) { if (Def->CreateFromCss(DefaultFont)) SetFont(Def, true); else DeleteObj(Def); } #endif FontCache = new LFontCache(this); SetScrollBars(false, false); } void LHtml::_Delete() { LAssert(!d->IsParsing); CssStore.Empty(); CssHref.Empty(); OpenTags.Length(0); Source.Reset(); DeleteObj(Tag); DeleteObj(FontCache); } LFont *LHtml::DefFont() { return GetFont(); } void LHtml::OnAddStyle(const char *MimeType, const char *Styles) { if (Styles) { const char *c = Styles; bool Status = CssStore.Parse(c); if (Status) { d->StyleDirty = true; } #if 0 // def _DEBUG bool LogCss = false; if (!Status) { char p[MAX_PATH_LEN]; sprintf_s(p, sizeof(p), "c:\\temp\\css_parse_failure_%i.txt", LRand()); LFile f; if (f.Open(p, O_WRITE)) { f.SetSize(0); if (CssStore.Error) f.Print("Error: %s\n\n", CssStore.Error.Get()); f.Write(Styles, strlen(Styles)); f.Close(); } } if (LogCss) { LStringPipe p; CssStore.Dump(p); LAutoString a(p.NewStr()); LFile f; if (f.Open("C:\\temp\\css.txt", O_WRITE)) { f.Write(a, strlen(a)); f.Close(); } } #endif } } void LHtml::ParseDocument(const char *Doc) { if (!Tag) { Tag = new LTag(this, 0); } if (GetCss()) GetCss()->DeleteProp(LCss::PropBackgroundColor); if (Tag) { Tag->TagId = ROOT; OpenTags.Length(0); if (IsHtml) { Parse(Tag, Doc); // Add body tag if not specified... LTag *Html = Tag->GetTagByName("html"); LTag *Body = Tag->GetTagByName("body"); if (!Html && !Body) { if ((Html = new LTag(this, 0))) Html->SetTag("html"); if ((Body = new LTag(this, Html))) Body->SetTag("body"); Html->Attach(Body); if (Tag->Text()) { LTag *Content = new LTag(this, Body); if (Content) { Content->TagId = CONTENT; Content->Text(NewStrW(Tag->Text())); } } while (Tag->Children.Length()) { LTag *t = ToTag(Tag->Children.First()); Body->Attach(t, Body->Children.Length()); } DeleteObj(Tag); Tag = Html; } else if (!Body) { if ((Body = new LTag(this, Html))) Body->SetTag("body"); for (unsigned i=0; iChildren.Length(); i++) { LTag *t = ToTag(Html->Children[i]); if (t->TagId != TAG_HEAD) { Body->Attach(t); i--; } } Html->Attach(Body); } if (Html && Body) { char16 *t = Tag->Text(); if (t) { if (ValidStrW(t)) { LTag *Content = new LTag(this, 0); if (Content) { Content->Text(NewStrW(Tag->Text())); Body->Attach(Content, 0); } } Tag->Text(0); } #if 0 // Enabling this breaks the test file 'gw2.html'. for (LTag *t = Html->Tags.First(); t; ) { if (t->Tag && t->Tag[0] == '!') { Tag->Attach(t, 0); t = Html->Tags.Current(); } else if (t->TagId != TAG_HEAD && t != Body) { if (t->TagId == TAG_HTML) { LTag *c; while ((c = t->Tags.First())) { Html->Attach(c, 0); } t->Detach(); DeleteObj(t); } else { t->Detach(); Body->Attach(t); } t = Html->Tags.Current(); } else { t = Html->Tags.Next(); } } #endif if (Environment) { const char *OnLoad; if (Body->Get("onload", OnLoad)) { Environment->OnExecuteScript(this, (char*)OnLoad); } } } } else { Tag->ParseText(Source); } } ViewWidth = -1; if (Tag) Tag->ResetCaches(); Invalidate(); } bool LHtml::NameW(const char16 *s) { LAutoPtr utf(WideToUtf8(s)); return Name(utf); } const char16 *LHtml::NameW() { LBase::Name(Source); return LBase::NameW(); } bool LHtml::Name(const char *s) { int Uid = -1; if (Environment) Uid = Environment->NextUid(); if (Uid < 0) Uid = GetDocumentUid() + 1; SetDocumentUid(Uid); _Delete(); _New(); IsHtml = false; // Detect HTML const char *c = s; while ((c = strchr(c, '<'))) { char *t = 0; c = ParseName((char*) ++c, &t); if (t && GetTagInfo(t)) { DeleteArray(t); IsHtml = true; break; } DeleteArray(t); } // Parse d->IsParsing = true; ParseDocument(s); d->IsParsing = false; if (Tag && d->StyleDirty) { d->StyleDirty = false; Tag->RestyleAll(); } if (d->DeferredLoads == 0) { OnLoad(); } Invalidate(); return true; } const char *LHtml::Name() { if (!Source && Tag) { LStringPipe s(1024); Tag->CreateSource(s); Source.Reset(s.NewStr()); } return Source; } LMessage::Result LHtml::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_COPY: { Copy(); break; } case M_JOBS_LOADED: { bool Update = false; int InitDeferredLoads = d->DeferredLoads; if (JobSem.Lock(_FL)) { for (unsigned i=0; iUserData); if (j->UserUid == MyUid && j->UserData != NULL) { Html1::LTag *r = static_cast(j->UserData); if (d->DeferredLoads > 0) d->DeferredLoads--; // Check the tag is still in our tree... if (Tag->HasChild(r)) { // Process the returned data... if (r->TagId == TAG_IMG) { if (j->pDC) { r->SetImage(j->Uri, j->pDC.Release()); ViewWidth = 0; Update = true; } else if (j->Stream) { LAutoPtr pDC(GdcD->Load(dynamic_cast(j->Stream.Get()))); if (pDC) { r->SetImage(j->Uri, pDC.Release()); ViewWidth = 0; Update = true; } else LgiTrace("%s:%i - Image decode failed for '%s'\n", _FL, j->Uri.Get()); } else if (j->Status == LDocumentEnv::LoadJob::JobOk) LgiTrace("%s:%i - Unexpected job type for '%s'\n", _FL, j->Uri.Get()); } else if (r->TagId == TAG_LINK) { if (!CssHref.Find(j->Uri)) { LStreamI *s = j->GetStream(); if (s) { s->ChangeThread(); int Size = (int)s->GetSize(); LAutoString Style(new char[Size+1]); ssize_t rd = s->Read(Style, Size); if (rd > 0) { Style[rd] = 0; CssHref.Add(j->Uri, true); OnAddStyle("text/css", Style); ViewWidth = 0; Update = true; } } } } else if (r->TagId == TAG_IFRAME) { // Remote IFRAME loading not support for security reasons. } else LgiTrace("%s:%i - Unexpected tag '%s' for URI '%s'\n", _FL, r->Tag.Get(), j->Uri.Get()); } else { /* Html1::LTag *p = ToTag(r->Parent); while (p && p->Parent) p = ToTag(p->Parent); */ LgiTrace("%s:%i - No child tag for job.\n", _FL); } } // else it's from another (historical) HTML control, ignore } JobSem.Jobs.DeleteObjects(); JobSem.Unlock(); } if (InitDeferredLoads > 0 && d->DeferredLoads <= 0) { LAssert(d->DeferredLoads == 0); d->DeferredLoads = 0; OnLoad(); } if (Update) { OnPosChange(); Invalidate(); } break; } } return LDocView::OnEvent(Msg); } int LHtml::OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_VSCROLL: { int LineY = GetFont()->GetHeight(); if (Tag) Tag->ClearToolTips(); if (n.Type == LNotifyScrollBarCreate && VScroll && LineY > 0) { int y = Y(); int p = MAX(y / LineY, 1); int fy = d->Content.y / LineY; VScroll->SetPage(p); VScroll->SetRange(fy); } Invalidate(); break; } default: { LTag *Ctrl = Tag ? Tag->FindCtrlId(c->GetId()) : NULL; if (Ctrl) return Ctrl->OnNotify(n); break; } } return LLayout::OnNotify(c, n); } void LHtml::OnPosChange() { LLayout::OnPosChange(); if (ViewWidth != X()) { Invalidate(); } } bool LHtml::OnLayout(LViewLayoutInfo &Inf) { if (!Inf.Width.Min) { Inf.Width.Min = Inf.FILL; Inf.Width.Max = Inf.FILL; } else { Inf.Height.Min = Inf.FILL; Inf.Height.Max = Inf.FILL; } return true; } LPoint LHtml::Layout(bool ForceLayout) { LRect Client = GetClient(); if (Tag && (ViewWidth != Client.X() || ForceLayout)) { LFlowRegion f(this, Client, false); // Flow text, width is different Tag->OnFlow(&f, 0); ViewWidth = Client.X(); d->Content.x = f.MAX.x + 1; d->Content.y = f.MAX.y + 1; // Set up scroll box bool Sy = f.y2 > Y(); int LineY = GetFont()->GetHeight(); uint64 Now = LCurrentTime(); if (Now - d->SetScrollTime > 100) { d->SetScrollTime = Now; SetScrollBars(false, Sy); if (Sy && VScroll && LineY > 0) { int y = Y(); int p = MAX(y / LineY, 1); int fy = f.y2 / LineY; VScroll->SetPage(p); VScroll->SetRange(fy); } } else { // LgiTrace("%s - Dropping SetScroll, loop detected: %i ms\n", GetClass(), (int)(Now - d->SetScrollTime)); } } return d->Content; } LPointF LHtml::GetDpiScale() { LPointF Scale(1.0, 1.0); auto Wnd = GetWindow(); if (Wnd) Scale = Wnd->GetDpiScale(); return Scale; } void LHtml::OnPaint(LSurface *ScreenDC) { // LProfile Prof("LHtml::OnPaint"); #if HTML_USE_DOUBLE_BUFFER LRect Client = GetClient(); if (ScreenDC->IsScreen()) { if (!MemDC || (MemDC->X() < Client.X() || MemDC->Y() < Client.Y())) { if (MemDC.Reset(new LMemDC)) { int Sx = Client.X() + 10; int Sy = Client.Y() + 10; if (!MemDC->Create(Sx, Sy, System32BitColourSpace)) { MemDC.Reset(); } } } if (MemDC) { MemDC->ClipRgn(NULL); #if 0//def _DEBUG MemDC->Colour(LColour(255, 0, 255)); MemDC->Rectangle(); #endif } } #endif LSurface *pDC = MemDC ? MemDC : ScreenDC; #if 0 Gtk::cairo_matrix_t mx; Gtk::cairo_get_matrix(pDC->Handle(), &mx); LPoint Offset; WindowVirtualOffset(&Offset); printf("\tHtml paint mx=%g,%g off=%i,%i\n", mx.x0, mx.y0, Offset.x, Offset.y); #endif LColour cBack; if (GetCss()) { LCss::ColorDef Bk = GetCss()->BackgroundColor(); if (Bk.Type == LCss::ColorRgb) cBack = Bk; } if (!cBack.IsValid()) cBack = LColour(Enabled() ? L_WORKSPACE : L_MED); pDC->Colour(cBack); pDC->Rectangle(); if (Tag) { Layout(); if (VScroll) { int LineY = GetFont()->GetHeight(); int Vs = (int)VScroll->Value(); pDC->SetOrigin(0, Vs * LineY); } bool InSelection = false; PaintStart = LCurrentTime(); d->MaxPaintTimeout = false; Tag->OnPaint(pDC, InSelection, 0); if (d->MaxPaintTimeout) { LgiTrace("%s:%i - Html max paint time reached: %i ms.\n", _FL, LCurrentTime() - PaintStart); } } #if HTML_USE_DOUBLE_BUFFER if (MemDC) { pDC->SetOrigin(0, 0); ScreenDC->Blt(0, 0, MemDC); } #endif if (d->OnLoadAnchor && VScroll) { LAutoString a = d->OnLoadAnchor; GotoAnchor(a); LAssert(d->OnLoadAnchor == 0); } } bool LHtml::HasSelection() { if (Cursor && Selection) { return Cursor->Cursor >= 0 && Selection->Selection >= 0 && !(Cursor == Selection && Cursor->Cursor == Selection->Selection); } return false; } void LHtml::UnSelectAll() { bool i = false; if (Cursor) { Cursor->Cursor = -1; Cursor = NULL; i = true; } if (Selection) { Selection->Selection = -1; Selection = NULL; i = true; } if (i) { Invalidate(); } } void LHtml::SelectAll() { } LTag *LHtml::GetLastChild(LTag *t) { if (t && t->Children.Length()) { for (LTag *i = ToTag(t->Children.Last()); i; ) { LTag *c = i->Children.Length() ? ToTag(i->Children.Last()) : NULL; if (c) i = c; else return i; } } return 0; } LTag *LHtml::PrevTag(LTag *t) { // This returns the previous tag in the tree as if all the tags were // listed via recursion using "in order". // Walk up the parent chain looking for a prev for (LTag *p = t; p; p = ToTag(p->Parent)) { // Does this tag have a parent? if (p->Parent) { // Prev? LTag *pp = ToTag(p->Parent); ssize_t Idx = pp->Children.IndexOf(p); LTag *Prev = Idx > 0 ? ToTag(pp->Children[Idx - 1]) : NULL; if (Prev) { LTag *Last = GetLastChild(Prev); return Last ? Last : Prev; } else { return ToTag(p->Parent); } } } return 0; } LTag *LHtml::NextTag(LTag *t) { // This returns the next tag in the tree as if all the tags were // listed via recursion using "in order". // Does this have a child tag? if (t->Children.Length() > 0) { return ToTag(t->Children.First()); } else { // Walk up the parent chain for (LTag *p = t; p; p = ToTag(p->Parent)) { // Does this tag have a next? if (p->Parent) { LTag *pp = ToTag(p->Parent); size_t Idx = pp->Children.IndexOf(p); LTag *Next = pp->Children.Length() > Idx + 1 ? ToTag(pp->Children[Idx + 1]) : NULL; if (Next) { return Next; } } } } return 0; } int LHtml::GetTagDepth(LTag *Tag) { // Returns the depth of the tag in the tree. int n = 0; for (LTag *t = Tag; t; t = ToTag(t->Parent)) { n++; } return n; } bool LHtml::IsCursorFirst() { if (!Cursor || !Selection) return false; return CompareTagPos(Cursor, Cursor->Cursor, Selection, Selection->Selection); } bool LHtml::CompareTagPos(LTag *a, ssize_t AIdx, LTag *b, ssize_t BIdx) { // Returns true if the 'a' is before 'b' point. if (!a || !b) return false; if (a == b) { return AIdx < BIdx; } else { LArray ATree, BTree; for (LTag *t = a; t; t = ToTag(t->Parent)) ATree.AddAt(0, t); for (LTag *t = b; t; t = ToTag(t->Parent)) BTree.AddAt(0, t); ssize_t Depth = MIN(ATree.Length(), BTree.Length()); for (int i=0; i 0); LTag *p = ATree[i-1]; LAssert(BTree[i-1] == p); ssize_t ai = p->Children.IndexOf(at); ssize_t bi = p->Children.IndexOf(bt); return ai < bi; } } } return false; } void LHtml::SetLoadImages(bool i) { if (i ^ GetLoadImages()) { LDocView::SetLoadImages(i); SendNotify(LNotifyShowImagesChanged); if (GetLoadImages() && Tag) { Tag->LoadImages(); } } } char *LHtml::GetSelection() { char *s = 0; if (Cursor && Selection) { LMemQueue p; bool InSelection = false; Tag->CopyClipboard(p, InSelection); int Len = (int)p.GetSize(); if (Len > 0) { char16 *t = (char16*)p.New(sizeof(char16)); if (t) { size_t Len = StrlenW(t); for (int i=0; i &t, LTag *Tag) { t.Add(Tag); for (unsigned i=0; iChildren.Length(); i++) { LTag *c = ToTag(Tag->Children[i]); BuildTagList(t, c); } } static void FormEncode(LStringPipe &p, const char *c) { const char *s = c; while (*c) { while (*c && *c != ' ') c++; if (c > s) { p.Write(s, c - s); c = s; } if (*c == ' ') { p.Write("+", 1); s = c; } else break; } } bool LHtml::OnSubmitForm(LTag *Form) { if (!Form || !Environment) { LAssert(!"Bad param"); return false; } const char *Method = NULL; const char *Action = NULL; if (!Form->Get("method", Method) || !Form->Get("action", Action)) { LAssert(!"Missing form action/method"); return false; } LHashTbl,char*> f; Form->CollectFormValues(f); bool Status = false; if (!_stricmp(Method, "post")) { LStringPipe p(256); bool First = true; // const char *Field; // for (char *Val = f.First(&Field); Val; Val = f.Next(&Field)) for (auto v : f) { if (First) First = false; else p.Write("&", 1); FormEncode(p, v.key); p.Write("=", 1); FormEncode(p, v.value); } LAutoPtr Data(p.NewStr()); Status = Environment->OnPostForm(this, Action, Data); } else if (!_stricmp(Method, "get")) { Status = Environment->OnNavigate(this, Action); } else { LAssert(!"Bad form method."); } f.DeleteArrays(); return Status; } bool LHtml::OnFind(LFindReplaceCommon *Params) { bool Status = false; if (Params) { if (!Params->Find) return Status; d->FindText.Reset(Utf8ToWide(Params->Find)); d->MatchCase = Params->MatchCase; } if (!Cursor) Cursor = Tag; if (Cursor && d->FindText) { LArray Tags; BuildTagList(Tags, Tag); ssize_t Start = Tags.IndexOf(Cursor); for (unsigned i=1; iText()) { char16 *Hit; if (d->MatchCase) Hit = StrstrW(s->Text(), d->FindText); else Hit = StristrW(s->Text(), d->FindText); if (Hit) { // found something... UnSelectAll(); Selection = Cursor = s; Cursor->Cursor = Hit - s->Text(); Selection->Selection = Cursor->Cursor + StrlenW(d->FindText); OnCursorChanged(); if (VScroll) { // Scroll the tag into view... int y = s->AbsY(); int LineY = GetFont()->GetHeight(); int Val = y / LineY; SetVScroll(Val); } Invalidate(); Status = true; break; } } } } return Status; } void LHtml::DoFind(std::function Callback) { LFindDlg *Dlg = new LFindDlg(this, [this](auto dlg, auto action) { OnFind(dlg); }); Dlg->DoModal(NULL); } bool LHtml::OnKey(LKey &k) { bool Status = false; if (k.Down()) { int Dy = 0; int LineY = GetFont()->GetHeight(); int Page = GetClient().Y() / LineY; switch (k.vkey) { case LK_F3: { OnFind(NULL); break; } #ifdef WIN32 case LK_INSERT: goto DoCopy; #endif case LK_UP: { Dy = -1; Status = true; break; } case LK_DOWN: { Dy = 1; Status = true; break; } case LK_PAGEUP: { Dy = -Page; Status = true; break; } case LK_PAGEDOWN: { Dy = Page; Status = true; break; } case LK_HOME: { Dy = (int) (VScroll ? -VScroll->Value() : 0); Status = true; break; } case LK_END: { if (VScroll) { LRange r = VScroll->GetRange(); Dy = (int)(r.End() - Page); } Status = true; break; } default: { switch (k.c16) { case 'f': case 'F': { if (k.CtrlCmd()) { DoFind(NULL); Status = true; } break; } case 'c': case 'C': { #ifdef WIN32 DoCopy: #endif if (k.CtrlCmd()) { Copy(); Status = true; } break; } } break; } } if (Dy && VScroll) SetVScroll(VScroll->Value() + Dy); } return Status; } int LHtml::ScrollY() { return GetFont()->GetHeight() * (VScroll ? (int)VScroll->Value() : 0); } void LHtml::OnMouseClick(LMouse &m) { Capture(m.Down()); SetPulse(m.Down() ? 200 : -1); if (m.Down()) { Focus(true); int Offset = ScrollY(); bool TagProcessedClick = false; LTagHit Hit; if (Tag) { Tag->GetTagByPos(Hit, m.x, m.y + Offset, 0, false, DEBUG_TAG_BY_POS); #if DEBUG_TAG_BY_POS Hit.Dump("MouseClick"); #endif } if (m.Left() && !m.IsContextMenu()) { if (m.Double()) { d->WordSelectMode = true; if (Cursor) { // Extend the selection out to the current word's boundaries. Selection = Cursor; Selection->Selection = Cursor->Cursor; if (Cursor->Text()) { ssize_t Base = Cursor->GetTextStart(); char16 *Text = Cursor->Text() + Base; while (Text[Cursor->Cursor]) { char16 c = Text[Cursor->Cursor]; if (strchr(WordDelim, c) || StrchrW(WhiteW, c)) break; Cursor->Cursor++; } } if (Selection->Text()) { ssize_t Base = Selection->GetTextStart(); char16 *Sel = Selection->Text() + Base; while (Selection->Selection > 0) { char16 c = Sel[Selection->Selection - 1]; if (strchr(WordDelim, c) || StrchrW(WhiteW, c)) break; Selection->Selection--; } } Invalidate(); SendNotify(LNotifySelectionChanged); } } else if (Hit.NearestText) { d->WordSelectMode = false; UnSelectAll(); Cursor = Hit.NearestText; Cursor->Cursor = Hit.Index; #if DEBUG_SELECTION LgiTrace("StartSelect Near='%20S' Idx=%i\n", Hit.NearestText->Text(), Hit.Index); #endif OnCursorChanged(); SendNotify(LNotifySelectionChanged); } else { #if DEBUG_SELECTION LgiTrace("StartSelect no text hit %p, %p\n", Cursor, Selection); #endif } } if (Hit.NearestText && Hit.Near == 0) { TagProcessedClick = Hit.NearestText->OnMouseClick(m); } else if (Hit.Direct) { TagProcessedClick = Hit.Direct->OnMouseClick(m); } #ifdef _DEBUG else if (m.Left() && m.Ctrl()) { LgiMsg(this, "No tag under the cursor.", GetClass()); } #endif if (!TagProcessedClick && m.IsContextMenu()) { LSubMenu RClick; enum ContextMenuCmds { IDM_DUMP = 100, IDM_COPY_SRC, IDM_VIEW_SRC, IDM_EXTERNAL, IDM_COPY, IDM_VIEW_IMAGES, }; #define IDM_CHARSET_BASE 10000 RClick.AppendItem (LLoadString(L_TEXTCTRL_COPY, "Copy"), IDM_COPY, HasSelection()); LMenuItem *Vs = RClick.AppendItem (LLoadString(L_VIEW_SOURCE, "View Source"), IDM_VIEW_SRC, Source != 0); RClick.AppendItem (LLoadString(L_COPY_SOURCE, "Copy Source"), IDM_COPY_SRC, Source != 0); LMenuItem *Load = RClick.AppendItem (LLoadString(L_VIEW_IMAGES, "View External Images"), IDM_VIEW_IMAGES, true); if (Load) Load->Checked(GetLoadImages()); RClick.AppendItem (LLoadString(L_VIEW_IN_DEFAULT_BROWSER, "View in Default Browser"), IDM_EXTERNAL, Source != 0); LSubMenu *Cs = RClick.AppendSub (LLoadString(L_CHANGE_CHARSET, "Change Charset")); if (Cs) { int n=0; for (LCharset *c = LGetCsList(); c->Charset; c++, n++) { Cs->AppendItem(c->Charset, IDM_CHARSET_BASE + n, c->IsAvailable()); } } if (!GetReadOnly() || // Is editor #ifdef _DEBUG 1 #else 0 #endif ) { RClick.AppendSeparator(); RClick.AppendItem("Dump Layout", IDM_DUMP, Tag != 0); } if (Vs) { Vs->Checked(!IsHtml); } if (OnContextMenuCreate(Hit, RClick) && GetMouse(m, true)) { int Id = RClick.Float(this, m.x, m.y); switch (Id) { case IDM_COPY: { Copy(); break; } case IDM_VIEW_SRC: { if (Vs) { DeleteObj(Tag); IsHtml = !IsHtml; ParseDocument(Source); } break; } case IDM_COPY_SRC: { if (Source) { LClipBoard c(this); const char *ViewCs = GetCharset(); if (ViewCs) { LAutoWString w((char16*)LNewConvertCp(LGI_WideCharset, Source, ViewCs)); if (w) c.TextW(w); } else c.Text(Source); } break; } case IDM_VIEW_IMAGES: { SetLoadImages(!GetLoadImages()); break; } case IDM_DUMP: { if (Tag) { LAutoWString s = Tag->DumpW(); if (s) { LClipBoard c(this); c.TextW(s); } } break; } case IDM_EXTERNAL: { if (!Source) { LgiTrace("%s:%i - No HTML source code.\n", _FL); break; } char Path[MAX_PATH_LEN]; if (!LGetSystemPath(LSP_TEMP, Path, sizeof(Path))) { LgiTrace("%s:%i - Failed to get the system path.\n", _FL); break; } char f[32]; sprintf_s(f, sizeof(f), "_%i.html", LRand(1000000)); LMakePath(Path, sizeof(Path), Path, f); LFile F; if (!F.Open(Path, O_WRITE)) { LgiTrace("%s:%i - Failed to open '%s' for writing.\n", _FL, Path); break; } LStringPipe Ex; bool Error = false; F.SetSize(0); LAutoWString SrcMem; const char *ViewCs = GetCharset(); if (ViewCs) SrcMem.Reset((char16*)LNewConvertCp(LGI_WideCharset, Source, ViewCs)); else SrcMem.Reset(Utf8ToWide(Source)); for (char16 *s=SrcMem; s && *s;) { char16 *cid = StristrW(s, L"cid:"); while (cid && !strchr("\'\"", cid[-1])) { cid = StristrW(cid+1, L"cid:"); } if (cid) { char16 Delim = cid[-1]; char16 *e = StrchrW(cid, Delim); if (e) { *e = 0; if (StrchrW(cid, '\n')) { *e = Delim; Error = true; break; } else { char File[MAX_PATH_LEN] = ""; if (Environment) { LDocumentEnv::LoadJob *j = Environment->NewJob(); if (j) { j->Uri.Reset(WideToUtf8(cid)); j->Env = Environment; j->Pref = LDocumentEnv::LoadJob::FmtFilename; j->UserUid = GetDocumentUid(); LDocumentEnv::LoadType Result = Environment->GetContent(j); if (Result == LDocumentEnv::LoadImmediate) { if (j->Filename) strcpy_s(File, sizeof(File), j->Filename); } else if (Result == LDocumentEnv::LoadDeferred) { d->DeferredLoads++; } DeleteObj(j); } } *e = Delim; Ex.Push(s, cid - s); if (File[0]) { char *d; while ((d = strchr(File, '\\'))) { *d = '/'; } Ex.Push(L"file:///"); LAutoWString w(Utf8ToWide(File)); Ex.Push(w); } s = e; } } else { Error = true; break; } } else { Ex.Push(s); break; } } if (!Error) { int64 WideChars = Ex.GetSize() / sizeof(char16); LAutoWString w(Ex.NewStrW()); LAutoString u(WideToUtf8(w, WideChars)); if (u) F.Write(u, strlen(u)); F.Close(); LString Err; if (!LExecute(Path, NULL, NULL, &Err)) { LgiMsg( this, "Failed to open '%s'\n%s", LAppInst ? LAppInst->LBase::Name() : GetClass(), MB_OK, Path, Err.Get()); } } break; } default: { if (Id >= IDM_CHARSET_BASE) { LCharset *c = LGetCsList() + (Id - IDM_CHARSET_BASE); if (c->Charset) { Charset = c->Charset; OverideDocCharset = true; char *Src = Source.Release(); _Delete(); _New(); Source.Reset(Src); ParseDocument(Source); Invalidate(); SendNotify(LNotifyCharsetChanged); } } else { OnContextMenuCommand(Hit, Id); } break; } } } } } else // Up Click { if (Selection && Cursor && Selection == Cursor && Selection->Selection == Cursor->Cursor) { Selection->Selection = -1; Selection = 0; SendNotify(LNotifySelectionChanged); #if DEBUG_SELECTION LgiTrace("NoSelect on release\n"); #endif } } } void LHtml::OnLoad() { d->IsLoaded = true; SendNotify(LNotifyDocLoaded); } LTag *LHtml::GetTagByPos(int x, int y, ssize_t *Index, LPoint *LocalCoords, bool DebugLog) { LTag *Status = NULL; if (Tag) { if (DebugLog) LgiTrace("GetTagByPos starting...\n"); LTagHit Hit; Tag->GetTagByPos(Hit, x, y, 0, DebugLog); if (DebugLog) LgiTrace("GetTagByPos Hit=%s, %i, %i...\n\n", Hit.Direct ? Hit.Direct->Tag.Get() : 0, Hit.Index, Hit.Near); Status = Hit.NearestText && Hit.Near == 0 ? Hit.NearestText : Hit.Direct; if (Hit.NearestText && Hit.Near < 30) { if (Index) *Index = Hit.Index; if (LocalCoords) *LocalCoords = Hit.LocalCoords; } } return Status; } void LHtml::SetVScroll(int64 v) { if (!VScroll) return; if (Tag) Tag->ClearToolTips(); VScroll->Value(v); Invalidate(); } bool LHtml::OnMouseWheel(double Lines) { if (VScroll) SetVScroll(VScroll->Value() + (int64)Lines); return true; } LCursor LHtml::GetCursor(int x, int y) { int Offset = ScrollY(); ssize_t Index = -1; LPoint LocalCoords; LTag *Tag = GetTagByPos(x, y + Offset, &Index, &LocalCoords); if (Tag) { LString Uri; if (LocalCoords.x >= 0 && LocalCoords.y >= 0 && Tag->IsAnchor(&Uri)) { LRect c = GetClient(); c.Offset(-c.x1, -c.y1); if (c.Overlap(x, y) && ValidStr(Uri)) { return LCUR_PointingHand; } } } return LCUR_Normal; } void LTag::ClearToolTips() { if (TipId) { Html->Tip.DeleteTip(TipId); TipId = 0; } for (auto c: Children) ToTag(c)->ClearToolTips(); } void LHtml::OnMouseMove(LMouse &m) { if (!Tag) return; int Offset = ScrollY(); LTagHit Hit; Tag->GetTagByPos(Hit, m.x, m.y + Offset, 0, false); if (!Hit.Direct && !Hit.NearestText) return; LString Uri; LTag *HitTag = Hit.NearestText && Hit.Near == 0 ? Hit.NearestText : Hit.Direct; if (HitTag && HitTag->TipId == 0 && Hit.LocalCoords.x >= 0 && Hit.LocalCoords.y >= 0 && HitTag->IsAnchor(&Uri) && Uri) { if (!Tip.GetParent()) { Tip.Attach(this); } LRect r = HitTag->GetRect(false); r.Offset(0, -Offset); if (!HitTag->TipId) HitTag->TipId = Tip.NewTip(Uri, r); // LgiTrace("NewTip: %s @ %s, ID=%i\n", Uri.Get(), r.GetStr(), HitTag->TipId); } if (IsCapturing() && Cursor && Hit.NearestText) { if (!Selection) { Selection = Cursor; Selection->Selection = Cursor->Cursor; Cursor = Hit.NearestText; Cursor->Cursor = Hit.Index; OnCursorChanged(); Invalidate(); SendNotify(LNotifySelectionChanged); #if DEBUG_SELECTION LgiTrace("CreateSelection '%20S' %i\n", Hit.NearestText->Text(), Hit.Index); #endif } else if ((Cursor != Hit.NearestText) || (Cursor->Cursor != Hit.Index)) { // Move the cursor to track the mouse if (Cursor) { Cursor->Cursor = -1; } Cursor = Hit.NearestText; Cursor->Cursor = Hit.Index; #if DEBUG_SELECTION LgiTrace("ExtendSelection '%20S' %i\n", Hit.NearestText->Text(), Hit.Index); #endif if (d->WordSelectMode && Cursor->Text()) { ssize_t Base = Cursor->GetTextStart(); if (IsCursorFirst()) { // Extend the cursor up the document to include the whole word while (Cursor->Cursor > 0) { char16 c = Cursor->Text()[Base + Cursor->Cursor - 1]; if (strchr(WordDelim, c) || StrchrW(WhiteW, c)) break; Cursor->Cursor--; } } else { // Extend the cursor down the document to include the whole word while (Cursor->Text()[Base + Cursor->Cursor]) { char16 c = Cursor->Text()[Base + Cursor->Cursor]; if (strchr(WordDelim, c) || StrchrW(WhiteW, c)) break; Cursor->Cursor++; } } } OnCursorChanged(); Invalidate(); SendNotify(LNotifySelectionChanged); } } } void LHtml::OnPulse() { if (VScroll && IsCapturing()) { int Fy = DefFont() ? DefFont()->GetHeight() : 16; LMouse m; if (GetMouse(m, false)) { LRect c = GetClient(); int Lines = 0; if (m.y < c.y1) { // Scroll up Lines = (c.y1 - m.y + Fy - 1) / -Fy; } else if (m.y > c.y2) { // Scroll down Lines = (m.y - c.y2 + Fy - 1) / Fy; } if (Lines && VScroll) SetVScroll(VScroll->Value() + Lines); } } } LRect *LHtml::GetCursorPos() { return &d->CursorPos; } void LHtml::SetCursorVis(bool b) { if (d->CursorVis ^ b) { d->CursorVis = b; Invalidate(); } } bool LHtml::GetCursorVis() { return d->CursorVis; } LDom *ElementById(LTag *t, char *id) { if (t && id) { const char *i; if (t->Get("id", i) && _stricmp(i, id) == 0) return t; for (unsigned i=0; iChildren.Length(); i++) { LTag *c = ToTag(t->Children[i]); LDom *n = ElementById(c, id); if (n) return n; } } return 0; } LDom *LHtml::getElementById(char *Id) { return ElementById(Tag, Id); } bool LHtml::GetLinkDoubleClick() { return d->LinkDoubleClick; } void LHtml::SetLinkDoubleClick(bool b) { d->LinkDoubleClick = b; } bool LHtml::GetFormattedContent(const char *MimeType, LString &Out, LArray *Media) { if (!MimeType) { LAssert(!"No MIME type for getting formatted content"); return false; } if (!_stricmp(MimeType, "text/html")) { // We can handle this type... LArray Imgs; if (Media) { // Find all the image tags... Tag->Find(TAG_IMG, Imgs); // Give them CID's if they don't already have them for (unsigned i=0; iGet("src", Src) && !Img->Get("cid", Cid)) { char id[256]; sprintf_s(id, sizeof(id), "%x.%x", (unsigned)LCurrentTime(), (unsigned)LRand()); Img->Set("cid", id); Img->Get("cid", Cid); } if (Src && Cid) { LFile *f = new LFile; if (f) { if (f->Open(Src, O_READ)) { // Add the exported image stream to the media array LDocView::ContentMedia &m = Media->New(); m.Id = Cid; m.Stream.Reset(f); } } } } } // Export the HTML, including the CID's from the first step Out = Name(); } else if (!_stricmp(MimeType, "text/plain")) { // Convert DOM tree down to text instead... LStringPipe p(512); if (Tag) { LTag::TextConvertState State(&p); Tag->ConvertToText(State); } Out = p.NewLStr(); } return false; } void LHtml::OnContent(LDocumentEnv::LoadJob *Res) { if (JobSem.Lock(_FL)) { JobSem.Jobs.Add(Res); JobSem.Unlock(); PostEvent(M_JOBS_LOADED); } } LHtmlElement *LHtml::CreateElement(LHtmlElement *Parent) { return new LTag(this, Parent); } bool LHtml::GetVariant(const char *Name, LVariant &Value, const char *Array) { if (!_stricmp(Name, "supportLists")) // Type: Bool Value = false; else if (!_stricmp(Name, "vml")) // Type: Bool // Vector Markup Language Value = false; else if (!_stricmp(Name, "mso")) // Type: Bool // mso = Microsoft Office Value = false; else return false; return true; } bool LHtml::EvaluateCondition(const char *Cond) { if (!Cond) return true; // This is a really bad attempt at writing an expression evaluator. // I could of course use the scripting language but that would pull // in a fairly large dependency on the HTML control. However user // apps that already have that could reimplement this virtual function // if they feel like it. LArray Str; for (const char *c = Cond; *c; ) { if (IsAlpha(*c)) { Str.Add(LTokStr(c)); } else if (IsWhiteSpace(*c)) { c++; } else { const char *e = c; while (*e && !IsWhiteSpace(*e) && !IsAlpha(*e)) e++; Str.Add(NewStr(c, e - c)); LAssert(e > c); if (e > c) c = e; else break; } } bool Result = true; bool Not = false; for (unsigned i=0; iGetAnchor(Name); if (a) { if (VScroll) { int LineY = GetFont()->GetHeight(); int Ay = a->AbsY(); int Scr = Ay / LineY; SetVScroll(Scr); VScroll->SendNotify(); } else d->OnLoadAnchor.Reset(NewStr(Name)); } } return false; } bool LHtml::GetEmoji() { return d->DecodeEmoji; } void LHtml::SetEmoji(bool i) { d->DecodeEmoji = i; } void LHtml::SetMaxPaintTime(int Ms) { d->MaxPaintTime = Ms; } bool LHtml::GetMaxPaintTimeout() { return d->MaxPaintTimeout; } //////////////////////////////////////////////////////////////////////// class LHtml_Factory : public LViewFactory { LView *NewView(const char *Class, LRect *Pos, const char *Text) { if (_stricmp(Class, "LHtml") == 0) { return new LHtml(-1, 0, 0, 100, 100, new LDefaultDocumentEnv); } return 0; } } LHtml_Factory; ////////////////////////////////////////////////////////////////////// struct BuildContext { LHtmlTableLayout *Layout; LTag *Table; LTag *TBody; LTag *CurTr; LTag *CurTd; int cx, cy; BuildContext() { Layout = NULL; cx = cy = 0; Table = NULL; TBody = NULL; CurTr = NULL; CurTd = NULL; } bool Build(LTag *t, int Depth) { bool RetReattach = false; switch (t->TagId) { case TAG_TABLE: { if (!Table) Table = t; else return false; break; } case TAG_TBODY: { if (TBody) return false; TBody = t; break; } case TAG_TR: { CurTr = t; break; } case TAG_TD: { CurTd = t; if (t->Parent != CurTr) { if ( !CurTr && (Table || TBody) ) { LTag *p = TBody ? TBody : Table; CurTr = new LTag(p->Html, p); if (CurTr) { CurTr->Tag.Reset(NewStr("tr")); CurTr->TagId = TAG_TR; ssize_t Idx = t->Parent->Children.IndexOf(t); t->Parent->Attach(CurTr, Idx); } } if (CurTr) { CurTr->Attach(t); RetReattach = true; } else { LAssert(0); return false; } } t->Cell->Pos.x = cx; t->Cell->Pos.y = cy; Layout->Set(t); break; } default: { if (CurTd == t->Parent) return false; break; } } for (unsigned n=0; nChildren.Length(); n++) { LTag *c = ToTag(t->Children[n]); bool Reattached = Build(c, Depth+1); if (Reattached) n--; } if (t->TagId == TAG_TR) { CurTr = NULL; cy++; cx = 0; Layout->s.y = cy; } if (t->TagId == TAG_TD) { CurTd = NULL; cx += t->Cell->Span.x; Layout->s.x = MAX(cx, Layout->s.x); } return RetReattach; } }; LHtmlTableLayout::LHtmlTableLayout(LTag *table) { Table = table; if (!Table) return; #if 0 BuildContext Ctx; Ctx.Layout = this; Ctx.Build(table, 0); #else int y = 0; LTag *FakeRow = 0; LTag *FakeCell = 0; LTag *r; for (size_t i=0; iChildren.Length(); i++) { r = ToTag(Table->Children[i]); if (r->Display() == LCss::DispNone) continue; if (r->TagId == TAG_TR) { FakeRow = 0; FakeCell = 0; } else if (r->TagId == TAG_TBODY) { ssize_t Index = Table->Children.IndexOf(r); for (size_t n=0; nChildren.Length(); n++) { LTag *t = ToTag(r->Children[n]); Table->Children.AddAt(++Index, t); t->Parent = Table; /* LgiTrace("Moving '%s'(%p) from TBODY(%p) into '%s'(%p)\n", t->Tag, t, r, t->Parent->Tag, t->Parent); */ } r->Children.Length(0); } else { if (!FakeRow) { if ((FakeRow = new LTag(Table->Html, 0))) { FakeRow->Tag.Reset(NewStr("tr")); FakeRow->TagId = TAG_TR; ssize_t Idx = Table->Children.IndexOf(r); Table->Attach(FakeRow, Idx); } } if (FakeRow) { if (!IsTableCell(r->TagId) && !FakeCell) { if ((FakeCell = new LTag(Table->Html, FakeRow))) { FakeCell->Tag.Reset(NewStr("td")); FakeCell->TagId = TAG_TD; if ((FakeCell->Cell = new LTag::TblCell)) { FakeCell->Cell->Span.x = 1; FakeCell->Cell->Span.y = 1; } } } ssize_t Idx = Table->Children.IndexOf(r); r->Detach(); if (IsTableCell(r->TagId)) { FakeRow->Attach(r); } else { LAssert(FakeCell != NULL); FakeCell->Attach(r); } i = Idx - 1; } } } FakeCell = NULL; for (size_t n=0; nChildren.Length(); n++) { LTag *r = ToTag(Table->Children[n]); if (r->TagId == TAG_TR) { int x = 0; for (size_t i=0; iChildren.Length(); i++) { LTag *cell = ToTag(r->Children[i]); if (!IsTableCell(cell->TagId)) { if (!FakeCell) { // Make a fake TD cell FakeCell = new LTag(Table->Html, NULL); FakeCell->Tag.Reset(NewStr("td")); FakeCell->TagId = TAG_TD; if ((FakeCell->Cell = new LTag::TblCell)) { FakeCell->Cell->Span.x = 1; FakeCell->Cell->Span.y = 1; } // Join the fake TD into the TR r->Children[i] = FakeCell; FakeCell->Parent = r; } else { // Not the first non-TD tag, so delete it from the TR. Only the // fake TD will remain in the TR. r->Children.DeleteAt(i--, true); } // Insert the tag into it as a child FakeCell->Children.Add(cell); cell->Parent = FakeCell; cell = FakeCell; } else { FakeCell = NULL; } if (IsTableCell(cell->TagId)) { if (cell->Display() == LCss::DispNone) continue; while (Get(x, y)) { x++; } cell->Cell->Pos.x = x; cell->Cell->Pos.y = y; Set(cell); x += cell->Cell->Span.x; } } y++; FakeCell = NULL; } } #endif } void LHtmlTableLayout::Dump() { int Sx, Sy; GetSize(Sx, Sy); LgiTrace("Table %i x %i cells.\n", Sx, Sy); for (int x=0; xCell->Pos.x, t->Cell->Pos.y, t->Cell->Span.x, t->Cell->Span.y); LgiTrace("%-10s", s); } LgiTrace("\n"); } LgiTrace("\n"); } void LHtmlTableLayout::GetAll(List &All) { LHashTbl, bool> Added; for (size_t y=0; y= (int) c.Length()) return NULL; CellArray &a = c[y]; if (x >= (int) a.Length()) return NULL; return a[x]; } bool LHtmlTableLayout::Set(LTag *t) { if (!t) return false; for (int y=0; yCell->Span.y; y++) { for (int x=0; xCell->Span.x; x++) { // LAssert(!c[y][x]); c[t->Cell->Pos.y + y][t->Cell->Pos.x + x] = t; } } return true; } void LTagHit::Dump(const char *Desc) { LArray d, n; LTag *t = Direct; unsigned i; for (i=0; i<3 && t; t = ToTag(t->Parent), i++) { d.AddAt(0, t); } t = NearestText; for (i=0; i<3 && t; t = ToTag(t->Parent), i++) { n.AddAt(0, t); } LgiTrace("Hit: %s Direct: ", Desc); for (i=0; i%s", d[i]->Tag ? d[i]->Tag.Get() : "CONTENT"); LgiTrace(" Nearest: "); for (i=0; i%s", n[i]->Tag ? n[i]->Tag.Get() : "CONTENT"); LgiTrace(" Local: %ix%i Index: %i Block: %s '%.10S'\n", LocalCoords.x, LocalCoords.y, Index, Block ? Block->GetStr() : NULL, Block ? Block->Text + Index : NULL); } diff --git a/src/common/Text/TextConvert.cpp b/src/common/Text/TextConvert.cpp --- a/src/common/Text/TextConvert.cpp +++ b/src/common/Text/TextConvert.cpp @@ -1,402 +1,474 @@ #include "lgi/common/Lgi.h" #include "lgi/common/TextConvert.h" #include "lgi/common/Mime.h" #include "lgi/common/Base64.h" #include "lgi/common/Charset.h" // return true if there are any characters with the 0x80 bit set bool Is8Bit(const char *Text) { if (!Text) return false; while (*Text) { if (*Text & 0x80) return true; Text++; } return false; } char ConvHexToBin(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c + 10 - 'a'; if (c >= 'A' && c <= 'F') return c + 10 - 'A'; return 0; } char *DecodeBase64Str(char *Str, ssize_t Len) { if (Str) { ssize_t B64Len = (Len < 0) ? strlen(Str) : Len; ssize_t BinLen = BufferLen_64ToBin(B64Len); char *s = new char[BinLen+1]; if (s) { ssize_t Converted = ConvertBase64ToBinary((uchar*)s, BinLen, Str, B64Len); s[Converted] = 0; DeleteArray(Str); Str = s; } } return Str; } LString LDecodeBase64Str(LString Str) { LString r; ssize_t BinLen = BufferLen_64ToBin(Str.Length()); - if (Str && r.Length(BinLen)) + if (Str && r.Length(BinLen) > 0) { ssize_t Converted = ConvertBase64ToBinary((uchar*)r.Get(), r.Length(), Str.Get(), Str.Length()); if (Converted >= 0) r.Get()[Converted] = 0; else r.Empty(); } + return r; } char *DecodeQuotedPrintableStr(char *Str, ssize_t Len) { if (Str) { if (Len < 0) Len = strlen(Str); uchar *s = new uchar[Len+1]; if (s) { char *Out = (char*) s; char *Text = Str; for (int i=0; i s) p.Write(s, e - s); break; } e++; } if (Decode) { // is there a word remaining bool Encoded = false; char *Start = e + 2; char *First = strchr(Start, '?'); char *Second = First ? strchr(First + 1, '?') : NULL; char *End = Second ? strstr(Second + 1, "?=") : NULL; if (End) { LString Cp(Start, First - Start); int Type = CONTENT_NONE; bool StripUnderscores = false; if (ToUpper(First[1]) == 'B') { // Base64 encoding Type = CONTENT_BASE64; } else if (ToUpper(First[1]) == 'Q') { // Quoted printable Type = CONTENT_QUOTED_PRINTABLE; StripUnderscores = true; } if (Type != CONTENT_NONE) { - Second++; - char *Block = NewStr(Second, End-Second); + Second++; + + LString Block(Second, End - Second); if (Block) { switch (Type) { case CONTENT_BASE64: - Block = DecodeBase64Str(Block); + Block = LDecodeBase64Str(Block); break; case CONTENT_QUOTED_PRINTABLE: - Block = DecodeQuotedPrintableStr(Block); + Block = LDecodeQuotedPrintableStr(Block); break; } - size_t Len = strlen(Block); if (StripUnderscores) { for (char *i=Block; *i; i++) { if (*i == '_') *i = ' '; } } if (Cp && !_stricmp(Cp, "utf-8")) { - p.Write((uchar*)Block, Len); + p.Write(Block); } else { auto Inst = LCharsetSystem::Inst(); - LString Detect = Inst && Inst->DetectCharset ? Inst->DetectCharset(LString(Block, Len)) : LString(); + LString Detect = Inst && Inst->DetectCharset ? Inst->DetectCharset(Block) : LString(); - LAutoString Utf8((char*)LNewConvertCp("utf-8", Block, Detect ? Detect : Cp, Len)); + LAutoString Utf8((char*)LNewConvertCp("utf-8", Block, Detect ? Detect : Cp, Block.Length())); if (Utf8) { if (LIsUtf8(Utf8)) p.Write((uchar*)Utf8.Get(), strlen(Utf8)); } else { - p.Write((uchar*)Block, Len); + p.Write(Block); } } - - DeleteArray(Block); } s = End + 2; if (*s == '\n') { s++; while (*s && strchr(WhiteSpace, *s)) s++; } Encoded = true; } } if (!Encoded) { // Encoding error, just emit the raw string and exit. size_t Len = strlen(s); p.Write((uchar*) s, Len); break; } } else if (Descape) { // Un-escape the string... e++; if (*e) p.Write(e, 1); else break; s = e + 1; } else { // Last segment of string... LAssert(*e == 0); if (e > s) p.Write(s, e - s); break; } } DeleteArray(Str); return p.NewStr(); } #define MIME_MAX_LINE 76 -char *EncodeRfc2047(char *Str, const char *CodePage, List *CharsetPrefs, ssize_t LineLength) +static void EncodeRfc2047_Impl( char *Str, size_t Length, + const char *Charset, + List *CharsetPrefs, + ssize_t LineLength, + std::function Process) { - if (!CodePage) - { - CodePage = "utf-8"; - } + if (!Str) + return; + + if (!Charset) + Charset = "utf-8"; LStringPipe p(256); - - if (!Str) - return NULL; - if (Is8Bit(Str)) { // pick an encoding bool Base64 = false; const char *DestCp = "utf-8"; - size_t Len = strlen(Str);; - if (_stricmp(CodePage, "utf-8") == 0) - { - DestCp = LUnicodeToCharset(Str, Len, CharsetPrefs); - } + if (Stricmp(Charset, "utf-8") == 0) + DestCp = LUnicodeToCharset(Str, Length, CharsetPrefs); int Chars = 0; - for (unsigned i=0; i 0 && - ((double)Chars/Len) > 0.4 + Length > 0 && + ((double)Chars/Length) > 0.4 ) ) { Base64 = true; } - char *Buf = (char*)LNewConvertCp(DestCp, Str, CodePage, Len); + char *Buf = (char*)LNewConvertCp(DestCp, Str, Charset, Length); if (Buf) { // encode the word char Prefix[64]; int Ch = sprintf_s(Prefix, sizeof(Prefix), "=?%s?%c?", DestCp, Base64 ? 'B' : 'Q'); p.Write(Prefix, Ch); LineLength += Ch; if (Base64) { // Base64 size_t InLen = strlen(Buf); // int EstBytes = BufferLen_BinTo64(InLen); char Temp[512]; ssize_t Bytes = ConvertBinaryToBase64(Temp, sizeof(Temp), (uchar*)Buf, InLen); p.Push(Temp, Bytes); } else { // Quoted printable for (char *w = Buf; *w; w++) { if (*w == ' ') { if (LineLength > MIME_MAX_LINE - 3) { p.Print("?=\r\n\t%s", Prefix); LineLength = 1 + strlen(Prefix); } p.Write((char*)"_", 1); LineLength++; } else if (*w & 0x80 || *w == '_' || *w == '?' || *w == '=') { if (LineLength > MIME_MAX_LINE - 5) { p.Print("?=\r\n\t%s", Prefix); LineLength = 1 + strlen(Prefix); } char Temp[16]; Ch = sprintf_s(Temp, sizeof(Temp), "=%2.2X", (uchar)*w); p.Write(Temp, Ch); LineLength += Ch; } else { if (LineLength > MIME_MAX_LINE - 3) { p.Print("?=\r\n\t%s", Prefix); LineLength = 1 + strlen(Prefix); } p.Write(w, 1); LineLength++; } } } p.Push("?="); DeleteArray(Buf); } - DeleteArray(Str); - Str = p.NewStr(); + Process(Str, p); } else { bool RecodeNewLines = false; for (char *s = Str; *s; s++) { if (*s == '\n' && (s == Str || s[-1] != '\r')) { RecodeNewLines = true; break; } } if (RecodeNewLines) { for (char *s = Str; *s; s++) { if (*s == '\r') ; else if (*s == '\n') p.Write("\r\n", 2); else p.Write(s, 1); } - DeleteArray(Str); - Str = p.NewStr(); + Process(Str, p); } } + // It's not an error to not call 'OutputStr', in that case + // the input is passed through to the output unchanged. +} + +// Old heap string encode method (will eventually remove this...) +char *EncodeRfc2047(char *Str, const char *Charset, List *CharsetPrefs, ssize_t LineLength) +{ + char *Out = Str; + + EncodeRfc2047_Impl( Str, Strlen(Str), + Charset, + CharsetPrefs, + LineLength, + [&Out](auto s, auto &pipe) + { + DeleteArray(s); + Out = pipe.NewStr(); + }); + + return Out; +} + +// New LString encode method +LString LEncodeRfc2047(LString Str, const char *Charset, List *CharsetPrefs, ssize_t LineLength) +{ + EncodeRfc2047_Impl( Str.Get(), Str.Length(), + Charset, + CharsetPrefs, + LineLength, + [&Str](auto s, auto &pipe) + { + Str = pipe.NewLStr(); + }); + return Str; } diff --git a/src/common/Widgets/Editor/ImageBlock.cpp b/src/common/Widgets/Editor/ImageBlock.cpp --- a/src/common/Widgets/Editor/ImageBlock.cpp +++ b/src/common/Widgets/Editor/ImageBlock.cpp @@ -1,1374 +1,1375 @@ #include "lgi/common/Lgi.h" #include "lgi/common/RichTextEdit.h" #include "lgi/common/GdcTools.h" #include "lgi/common/Menu.h" #include "lgi/common/Net.h" +#include "lgi/common/Uri.h" #include "RichTextEditPriv.h" #define LOADER_THREAD_LOGGING 1 #define TIMEOUT_LOAD_PROGRESS 100 // ms int ImgScales[] = { 15, 25, 50, 75, 100 }; class ImageLoader : public LEventTargetThread, public Progress { LString File; LEventSinkI *Sink = NULL; LSurface *Img = NULL; LAutoPtr Filter; bool SurfaceSent = false; int64 Ts = 0; LAutoPtr In; public: ImageLoader(LEventSinkI *s) : LEventTargetThread("ImageLoader") { } ~ImageLoader() { Progress::Cancel(true); #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - ~ImageLoader\n", _FL); #endif } void Value(int64 v) { Progress::Value(v); if (!SurfaceSent) { SurfaceSent = true; PostSink(M_IMAGE_SET_SURFACE, (LMessage::Param)Img, (LMessage::Param)In.Release()); } int64 Now = LCurrentTime(); if (Now - Ts > TIMEOUT_LOAD_PROGRESS) { Ts = Now; PostSink(M_IMAGE_PROGRESS, (LMessage::Param)v); } } bool PostSink(int Cmd, LMessage::Param a = 0, LMessage::Param b = 0) { for (int i=0; i<50; i++) { if (Sink->PostEvent(Cmd, a, b)) return true; LSleep(1); } LAssert(!"PostSink failed."); return false; } LMessage::Result OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_IMAGE_LOAD_FILE: { LAutoPtr Str((LString*)Msg->A()); File = *Str; #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Receive(M_IMAGE_LOAD_FILE): '%s'\n", _FL, File.Get()); #endif Filter = LFilterFactory::New(File, O_READ, NULL); if (!Filter) { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_ERROR): no filter\n", _FL); #endif return PostSink(M_IMAGE_ERROR); } if (!In.Reset(new LFile) || !In->Open(File, O_READ)) { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_ERROR): can't read\n", _FL); #endif return PostSink(M_IMAGE_ERROR); } if (!(Img = new LMemDC)) { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_ERROR): alloc err\n", _FL); #endif return PostSink(M_IMAGE_ERROR); } Filter->SetProgress(this); Ts = LCurrentTime(); LFilter::IoStatus Status = Filter->ReadImage(Img, In); if (Status != LFilter::IoSuccess) { if (Status == LFilter::IoComponentMissing) { LString *s = new LString(Filter->GetComponentName()); #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_COMPONENT_MISSING)\n", _FL); #endif return PostSink(M_IMAGE_COMPONENT_MISSING, (LMessage::Param)s); } #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_ERROR): Filter::ReadImage err\n", _FL); #endif return PostSink(M_IMAGE_ERROR); } if (!SurfaceSent) { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_SET_SURFACE)\n", _FL); #endif PostSink(M_IMAGE_SET_SURFACE, (LMessage::Param)Img, (LMessage::Param)In.Release()); } #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_FINISHED)\n", _FL); #endif PostSink(M_IMAGE_FINISHED); break; } case M_IMAGE_LOAD_STREAM: { LAutoPtr Stream((LStreamI*)Msg->A()); LAutoPtr FileName((LString*)Msg->B()); #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Receive(M_IMAGE_LOAD_STREAM)\n", _FL); #endif if (!Stream) { LAssert(!"No stream."); return PostSink(M_IMAGE_ERROR); } LMemStream *Mem = new LMemStream(Stream, 0, -1); In.Reset(Mem); Filter = LFilterFactory::New(FileName ? *FileName : 0, O_READ, (const uchar*)Mem->GetBasePtr()); if (!Filter) { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_ERROR): no filter\n", _FL); #endif return PostSink(M_IMAGE_ERROR); } if (!(Img = new LMemDC)) { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_ERROR): alloc err\n", _FL); #endif return PostSink(M_IMAGE_ERROR); } Filter->SetProgress(this); Ts = LCurrentTime(); LFilter::IoStatus Status = Filter->ReadImage(Img, Mem); if (Status != LFilter::IoSuccess) { if (Status == LFilter::IoComponentMissing) { LString *s = new LString(Filter->GetComponentName()); #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_COMPONENT_MISSING)\n", _FL); #endif return PostSink(M_IMAGE_COMPONENT_MISSING, (LMessage::Param)s); } #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_ERROR): Filter::ReadImage err\n", _FL); #endif return PostSink(M_IMAGE_ERROR); } if (!SurfaceSent) { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_SET_SURFACE)\n", _FL); #endif PostSink(M_IMAGE_SET_SURFACE, (LMessage::Param)Img, (LMessage::Param)In.Release()); } #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_FINISHED)\n", _FL); #endif PostSink(M_IMAGE_FINISHED); break; } case M_IMAGE_RESAMPLE: { LSurface *Dst = (LSurface*) Msg->A(); LSurface *Src = (LSurface*) Msg->B(); #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Receive(M_IMAGE_RESAMPLE)\n", _FL); #endif if (Src && Dst) { ResampleDC(Dst, Src); if (PostSink(M_IMAGE_RESAMPLE)) { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_RESAMPLE)\n", _FL); #endif } else LgiTrace("%s:%i - Error sending re-sample msg.\n", _FL); } else { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_ERROR): ptr err %p %p\n", _FL, Src, Dst); #endif return PostSink(M_IMAGE_ERROR); } break; } case M_IMAGE_COMPRESS: { LSurface *img = (LSurface*)Msg->A(); LRichTextPriv::ImageBlock::ScaleInf *si = (LRichTextPriv::ImageBlock::ScaleInf*)Msg->B(); #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Receive(M_IMAGE_COMPRESS)\n", _FL); #endif if (!img || !si) { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_ERROR): invalid ptr\n", _FL); #endif PostSink(M_IMAGE_ERROR, (LMessage::Param) new LString("Invalid pointer.")); break; } auto f = LFilterFactory::New("a.jpg", O_READ, NULL); if (!f) { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_ERROR): No JPEG filter available\n", _FL); #endif PostSink(M_IMAGE_ERROR, (LMessage::Param) new LString("No JPEG filter available.")); break; } LAutoPtr scaled; if (img->X() != si->Sz.x || img->Y() != si->Sz.y) { if (!scaled.Reset(new LMemDC(si->Sz.x, si->Sz.y, img->GetColourSpace()))) break; ResampleDC(scaled, img, NULL, NULL); img = scaled; } LXmlTag Props; f->Props = &Props; Props.SetAttr(LGI_FILTER_QUALITY, RICH_TEXT_RESIZED_JPEG_QUALITY); LAutoPtr jpg(new LMemStream(1024)); if (!f->WriteImage(jpg, img)) { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_ERROR): Image compression failed\n", _FL); #endif PostSink(M_IMAGE_ERROR, (LMessage::Param) new LString("Image compression failed.")); break; } #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_COMPRESS)\n", _FL); #endif PostSink(M_IMAGE_COMPRESS, (LMessage::Param)jpg.Release(), (LMessage::Param)si); break; } case M_IMAGE_ROTATE: { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Receive(M_IMAGE_ROTATE)\n", _FL); #endif LSurface *Img = (LSurface*)Msg->A(); if (!Img) { LAssert(!"No image."); break; } RotateDC(Img, Msg->B() == 1 ? 90 : 270); #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_ROTATE)\n", _FL); #endif PostSink(M_IMAGE_ROTATE); break; } case M_IMAGE_FLIP: { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Receive(M_IMAGE_FLIP)\n", _FL); #endif LSurface *Img = (LSurface*)Msg->A(); if (!Img) { LAssert(!"No image."); break; } if (Msg->B() == 1) FlipXDC(Img); else FlipYDC(Img); #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_FLIP)\n", _FL); #endif PostSink(M_IMAGE_FLIP); break; } case M_CLOSE: { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Receive(M_CLOSE)\n", _FL); #endif EndThread(); break; } } return 0; } }; LRichTextPriv::ImageBlock::ImageBlock(LRichTextPriv *priv) : Block(priv) { ThreadHnd = 0; IsDeleted = false; LayoutDirty = false; Pos.ZOff(-1, -1); Style = NULL; Size.x = 200; Size.y = 64; Scale = 1; SourceValid.ZOff(-1, -1); ResizeIdx = -1; ThreadBusy = 0; Margin.ZOff(0, 0); Border.ZOff(0, 0); Padding.ZOff(0, 0); } LRichTextPriv::ImageBlock::ImageBlock(const ImageBlock *Copy) : Block(Copy->d) { ThreadHnd = 0; ThreadBusy = 0; LayoutDirty = true; SourceImg.Reset(new LMemDC(Copy->SourceImg)); Size = Copy->Size; IsDeleted = false; Margin = Copy->Margin; Border = Copy->Border; Padding = Copy->Padding; } LRichTextPriv::ImageBlock::~ImageBlock() { LAssert(ThreadBusy == 0); if (ThreadHnd) PostThreadEvent(ThreadHnd, M_CLOSE); LAssert(Cursors == 0); } bool LRichTextPriv::ImageBlock::IsValid() { return true; } bool LRichTextPriv::ImageBlock::IsBusy(bool Stop) { return ThreadBusy != 0; } bool LRichTextPriv::ImageBlock::SetImage(LAutoPtr Img) { SourceImg = Img; if (!SourceImg) return false; Scales.Length(CountOf(ImgScales)); for (int i=0; iX() * ImgScales[i] / 100; si.Sz.y = SourceImg->Y() * ImgScales[i] / 100; si.Percent = ImgScales[i]; if (si.Sz.x == SourceImg->X() && si.Sz.y == SourceImg->Y()) { ResizeIdx = i; } } LayoutDirty = true; UpdateDisplayImg(); if (DisplayImg) { // Update the display image by scaling it from the source... if (PostThreadEvent(GetThreadHandle(), M_IMAGE_RESAMPLE, (LMessage::Param) DisplayImg.Get(), (LMessage::Param) SourceImg.Get())) UpdateThreadBusy(_FL, 1); } else LayoutDirty = true; // Also create a JPG for the current scale (needed before // we save to HTML). if (ResizeIdx >= 0 && ResizeIdx < (int)Scales.Length()) { ScaleInf &si = Scales[ResizeIdx]; if (PostThreadEvent(GetThreadHandle(), M_IMAGE_COMPRESS, (LMessage::Param)SourceImg.Get(), (LMessage::Param)&si)) UpdateThreadBusy(_FL, 1); } else LAssert(!"ResizeIdx should be valid."); return true; } bool LRichTextPriv::ImageBlock::Load(const char *Src) { if (Src) Source = Src; LAutoPtr Stream; LString::Array a = Source.Strip().Split(":", 1); if (a.Length() > 1 && a[0].Equals("cid")) { LDocumentEnv *Env = d->View->GetEnv(); if (!Env) return false; LDocumentEnv::LoadJob *j = Env->NewJob(); if (!j) return false; j->Uri.Reset(NewStr(Source)); j->Env = Env; j->Pref = LDocumentEnv::LoadJob::FmtStream; j->UserUid = d->View->GetDocumentUid(); LDocumentEnv::LoadType Result = Env->GetContent(j); if (Result == LDocumentEnv::LoadImmediate) { StreamMimeType = j->MimeType; ContentId = j->ContentId.Strip("<>"); FileName = j->Filename; if (j->Stream) { Stream = j->Stream; } else if (j->pDC) { SourceImg = j->pDC; return true; } } else if (Result == LDocumentEnv::LoadDeferred) { LAssert(!"Impl me?"); } DeleteObj(j); } else if (LFileExists(Source)) { FileName = Source; FileMimeType = LAppInst->GetFileMimeType(Source); } else return false; if (!FileName && !Stream) return false; if (Stream) { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Posting M_IMAGE_LOAD_STREAM\n", _FL); #endif if (PostThreadEvent(GetThreadHandle(), M_IMAGE_LOAD_STREAM, (LMessage::Param)Stream.Release(), (LMessage::Param) (FileName ? new LString(FileName) : NULL))) { UpdateThreadBusy(_FL, 1); return true; } } if (FileName) { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Posting M_IMAGE_LOAD_FILE\n", _FL); #endif if (PostThreadEvent(GetThreadHandle(), M_IMAGE_LOAD_FILE, (LMessage::Param)new LString(FileName))) { UpdateThreadBusy(_FL, 1); return true; } } return false; } int LRichTextPriv::ImageBlock::GetLines() { return 1; } bool LRichTextPriv::ImageBlock::OffsetToLine(ssize_t Offset, int *ColX, LArray *LineY) { if (ColX) *ColX = Offset > 0; if (LineY) LineY->Add(0); return true; } ssize_t LRichTextPriv::ImageBlock::LineToOffset(ssize_t Line) { return 0; } void LRichTextPriv::ImageBlock::Dump() { } LNamedStyle *LRichTextPriv::ImageBlock::GetStyle(ssize_t At) { return Style; } void LRichTextPriv::ImageBlock::SetStyle(LNamedStyle *s) { if ((Style = s)) { LFont *Fnt = d->GetFont(s); LayoutDirty = true; LAssert(Fnt != NULL); Margin.x1 = Style->MarginLeft().ToPx(Pos.X(), Fnt); Margin.y1 = Style->MarginTop().ToPx(Pos.Y(), Fnt); Margin.x2 = Style->MarginRight().ToPx(Pos.X(), Fnt); Margin.y2 = Style->MarginBottom().ToPx(Pos.Y(), Fnt); Border.x1 = Style->BorderLeft().ToPx(Pos.X(), Fnt); Border.y1 = Style->BorderTop().ToPx(Pos.Y(), Fnt); Border.x2 = Style->BorderRight().ToPx(Pos.X(), Fnt); Border.y2 = Style->BorderBottom().ToPx(Pos.Y(), Fnt); Padding.x1 = Style->PaddingLeft().ToPx(Pos.X(), Fnt); Padding.y1 = Style->PaddingTop().ToPx(Pos.Y(), Fnt); Padding.x2 = Style->PaddingRight().ToPx(Pos.X(), Fnt); Padding.y2 = Style->PaddingBottom().ToPx(Pos.Y(), Fnt); } } ssize_t LRichTextPriv::ImageBlock::Length() { return IsDeleted ? 0 : 1; } bool LRichTextPriv::ImageBlock::ToHtml(LStream &s, LArray *Media, LRange *Rng) { LUri uri(Source); if (uri.IsProtocol("http") || uri.IsProtocol("https") || uri.IsProtocol("ftp")) { // Nothing to do...? } else if (Media) { bool ValidSourceFile = LFileExists(Source); LDocView::ContentMedia &Cm = Media->New(); int Idx = LRand() % 10000; if (!ContentId) ContentId.Printf("%u@memecode.com", Idx); Cm.Id = ContentId; LString Style; ScaleInf *Si = ResizeIdx >= 0 && ResizeIdx < (int)Scales.Length() ? &Scales[ResizeIdx] : NULL; if (Si && Si->Compressed) { // Attach a copy of the resized JPEG... Si->Compressed->SetPos(0); Cm.Stream.Reset(new LMemStream(Si->Compressed, 0, -1)); Cm.MimeType = Si->MimeType; if (FileName) Cm.FileName = LGetLeaf(FileName); else if (Cm.MimeType.Equals("image/jpeg")) Cm.FileName.Printf("img%u.jpg", Idx); else if (Cm.MimeType.Equals("image/png")) Cm.FileName.Printf("img%u.png", Idx); else if (Cm.MimeType.Equals("image/tiff")) Cm.FileName.Printf("img%u.tiff", Idx); else if (Cm.MimeType.Equals("image/gif")) Cm.FileName.Printf("img%u.gif", Idx); else if (Cm.MimeType.Equals("image/bmp")) Cm.FileName.Printf("img%u.bmp", Idx); else { LAssert(!"Unknown image mime type?"); Cm.FileName.Printf("img%u", Idx); } } else if (ValidSourceFile) { // Attach the original file... Cm.MimeType = LAppInst->GetFileMimeType(Source); Cm.FileName = LGetLeaf(Source); LFile *f = new LFile; if (f) { if (f->Open(Source, O_READ)) { Cm.Stream.Reset(f); } else { delete f; LgiTrace("%s:%i - Failed to open link image '%s'.\n", _FL, Source.Get()); } } } else { LAssert(!"No valid source."); return false; } LAssert(Cm.MimeType != NULL); if (DisplayImg && SourceImg && DisplayImg->X() != SourceImg->X()) { int Dx = DisplayImg->X(); Style.Printf(" style=\"width:%ipx\"", Dx); } if (Cm.Stream) { s.Print("HtmlLinkAsCid) s.Print("cid:%s", Cm.Id.Get()); else s.Print("%s", Cm.FileName.Get()); s.Print("\">\n"); LAssert(Cm.Valid()); return true; } } s.Print("\n", Source.Get()); return true; } bool LRichTextPriv::ImageBlock::GetPosFromIndex(BlockCursor *Cursor) { if (!Cursor) return d->Error(_FL, "No cursor param."); if (LayoutDirty) { Cursor->Pos.ZOff(-1, -1); // This is valid behaviour... need to // wait for layout before getting cursor // position. return false; } Cursor->Pos = ImgPos; Cursor->Line = Pos; if (Cursor->Offset == 0) { Cursor->Pos.x2 = Cursor->Pos.x1 + 1; } else if (Cursor->Offset == 1) { Cursor->Pos.x1 = Cursor->Pos.x2 - 1; } return true; } bool LRichTextPriv::ImageBlock::HitTest(HitTestResult &htr) { if (htr.In.y < Pos.y1 || htr.In.y > Pos.y2) return false; htr.Near = false; htr.LineHint = 0; int Cx = ImgPos.x1 + (ImgPos.X() / 2); if (htr.In.x < Cx) htr.Idx = 0; else htr.Idx = 1; return true; } void LRichTextPriv::ImageBlock::OnPaint(PaintContext &Ctx) { bool ImgSelected = Ctx.SelectBeforePaint(this); // Paint margins, borders and padding... LRect r = Pos; r.x1 -= Margin.x1; r.y1 -= Margin.y1; r.x2 -= Margin.x2; r.y2 -= Margin.y2; LCss::ColorDef BorderStyle; if (Style) BorderStyle = Style->BorderLeft().Color; LColour BorderCol(222, 222, 222); if (BorderStyle.Type == LCss::ColorRgb) BorderCol.Set(BorderStyle.Rgb32, 32); Ctx.DrawBox(r, Margin, Ctx.Colours[Unselected].Back); Ctx.DrawBox(r, Border, BorderCol); Ctx.DrawBox(r, Padding, Ctx.Colours[Unselected].Back); if (!DisplayImg && SourceImg && SourceImg->X() > r.X()) { UpdateDisplayImg(); } LSurface *Src = DisplayImg ? DisplayImg : SourceImg; if (Src) { if (SourceValid.Valid()) { LRect Bounds(0, 0, Size.x-1, Size.y-1); Bounds.Offset(r.x1, r.y1); Ctx.pDC->Colour(L_MED); Ctx.pDC->Box(&Bounds); Bounds.Inset(1, 1); Ctx.pDC->Colour(L_WORKSPACE); Ctx.pDC->Rectangle(&Bounds); LRect rr(0, 0, Src->X()-1, SourceValid.y2 / Scale); Ctx.pDC->Blt(r.x1, r.y1, Src, &rr); } else { if (Ctx.Type == LRichTextPriv::Selected) { if (!SelectImg && SelectImg.Reset(new LMemDC(Src->X(), Src->Y(), System32BitColourSpace))) { SelectImg->Blt(0, 0, Src); int Op = SelectImg->Op(GDC_ALPHA); LColour c = Ctx.Colours[LRichTextPriv::Selected].Back; c.Rgb(c.r(), c.g(), c.b(), 0xa0); SelectImg->Colour(c); SelectImg->Rectangle(); SelectImg->Op(Op); } Ctx.pDC->Blt(r.x1, r.y1, SelectImg); } else { Ctx.pDC->Blt(r.x1, r.y1, Src); } } } else { // Drag missing image... r = ImgPos; LColour cBack(245, 245, 245); Ctx.pDC->Colour(ImgSelected ? cBack.Mix(Ctx.Colours[Selected].Back) : cBack); Ctx.pDC->Rectangle(&r); Ctx.pDC->Colour(L_LOW); uint Ls = Ctx.pDC->LineStyle(LSurface::LineAlternate); Ctx.pDC->Box(&r); Ctx.pDC->LineStyle(Ls); int Cx = r.x1 + (r.X() >> 1); int Cy = r.y1 + (r.Y() >> 1); Ctx.pDC->Colour(LColour::Red); int Sz = 5; Ctx.pDC->Line(Cx - Sz, Cy - Sz, Cx + Sz, Cy + Sz); Ctx.pDC->Line(Cx - Sz, Cy - Sz + 1, Cx + Sz - 1, Cy + Sz); Ctx.pDC->Line(Cx - Sz + 1, Cy - Sz, Cx + Sz, Cy + Sz - 1); Ctx.pDC->Line(Cx + Sz, Cy - Sz, Cx - Sz, Cy + Sz); Ctx.pDC->Line(Cx + Sz - 1, Cy - Sz, Cx - Sz, Cy + Sz - 1); Ctx.pDC->Line(Cx + Sz, Cy - Sz + 1, Cx - Sz + 1, Cy + Sz); } ImgSelected = Ctx.SelectAfterPaint(this); if (ImgSelected) { Ctx.pDC->Colour(Ctx.Colours[Selected].Back); Ctx.pDC->Rectangle(ImgPos.x2 + 1, ImgPos.y1, ImgPos.x2 + 7, ImgPos.y2); } if (Ctx.Cursor && Ctx.Cursor->Blk == this && Ctx.Cursor->Blink && d->View->Focus()) { Ctx.pDC->Colour(CursorColour); if (Ctx.Cursor->Pos.Valid()) Ctx.pDC->Rectangle(&Ctx.Cursor->Pos); else Ctx.pDC->Rectangle(Pos.x1, Pos.y1, Pos.x1, Pos.y2); } } bool LRichTextPriv::ImageBlock::OnLayout(Flow &flow) { LayoutDirty = false; flow.Left += Margin.x1; flow.Right -= Margin.x2; flow.CurY += Margin.y1; Pos.x1 = flow.Left; Pos.y1 = flow.CurY; Pos.x2 = flow.Right; Pos.y2 = flow.CurY-1; // Start with a 0px height. flow.Left += Border.x1 + Padding.x1; flow.Right -= Border.x2 + Padding.x2; flow.CurY += Border.y1 + Padding.y1; ImgPos.x1 = Pos.x1 + Padding.x1; ImgPos.y1 = Pos.y1 + Padding.y1; ImgPos.x2 = ImgPos.x1 + Size.x - 1; ImgPos.y2 = ImgPos.y1 + Size.y - 1; int Px2 = ImgPos.x2 + Padding.x2; if (Px2 < Pos.x2) Pos.x2 = ImgPos.x2 + Padding.x2; Pos.y2 = ImgPos.y2 + Padding.y2; flow.CurY = Pos.y2 + 1 + Margin.y2 + Border.y2 + Padding.y2; flow.Left -= Margin.x1 + Border.x1 + Padding.x1; flow.Right += Margin.x2 + Border.x2 + Padding.x2; return true; } ssize_t LRichTextPriv::ImageBlock::GetTextAt(ssize_t Offset, LArray &t) { // No text to get return 0; } ssize_t LRichTextPriv::ImageBlock::CopyAt(ssize_t Offset, ssize_t Chars, LArray *Text) { // No text to copy return 0; } bool LRichTextPriv::ImageBlock::Seek(SeekType To, BlockCursor &Cursor) { switch (To) { case SkLineStart: { Cursor.Offset = 0; Cursor.LineHint = 0; break; } case SkLineEnd: { Cursor.Offset = 1; Cursor.LineHint = 0; break; } case SkLeftChar: { if (Cursor.Offset != 1) return false; Cursor.Offset = 0; Cursor.LineHint = 0; break; } case SkRightChar: { if (Cursor.Offset != 0) return false; Cursor.Offset = 1; Cursor.LineHint = 0; break; } default: { return false; break; } } return true; } ssize_t LRichTextPriv::ImageBlock::FindAt(ssize_t StartIdx, const uint32_t *Str, LFindReplaceCommon *Params) { // No text to find in return -1; } void LRichTextPriv::ImageBlock::IncAllStyleRefs() { if (Style) Style->RefCount++; } bool LRichTextPriv::ImageBlock::DoContext(LSubMenu &s, LPoint Doc, ssize_t Offset, bool TopOfMenu) { if (SourceImg && !TopOfMenu) { s.AppendSeparator(); LSubMenu *c = s.AppendSub("Transform Image"); if (c) { c->AppendItem("Rotate Clockwise", IDM_CLOCKWISE); c->AppendItem("Rotate Anti-clockwise", IDM_ANTI_CLOCKWISE); c->AppendItem("Horizontal Flip", IDM_X_FLIP); c->AppendItem("Vertical Flip", IDM_Y_FLIP); } c = s.AppendSub("Scale Image"); if (c) { for (unsigned i=0; iX() * ImgScales[i] / 100; si.Sz.y = SourceImg->Y() * ImgScales[i] / 100; si.Percent = ImgScales[i]; m.Printf("%i x %i, %i%% ", si.Sz.x, si.Sz.y, ImgScales[i]); if (si.Compressed) { char Sz[128]; LFormatSize(Sz, sizeof(Sz), si.Compressed->GetSize()); LString s; s.Printf(" (%s)", Sz); m += s; } LMenuItem *mi = c->AppendItem(m, IDM_SCALE_IMAGE+i, !IsBusy()); if (mi && ResizeIdx == i) { mi->Checked(true); } } } return true; } return false; } LRichTextPriv::Block *LRichTextPriv::ImageBlock::Clone() { return new ImageBlock(this); } void LRichTextPriv::ImageBlock::OnComponentInstall(LString Name) { if (Source && !SourceImg) { // Retry the load? Load(Source); } } void LRichTextPriv::ImageBlock::UpdateDisplay(int yy) { LRect s; if (DisplayImg && !SourceValid.Valid()) { SourceValid = SourceImg->Bounds(); SourceValid.y2 = yy; s = SourceValid; } else { s = SourceValid; s.y1 = s.y2 + 1; s.y2 = SourceValid.y2 = yy; } if (DisplayImg) { LRect d(0, s.y1 / Scale, DisplayImg->X()-1, s.y2 / Scale); // Do a quick and dirty nearest neighbor scale to // show the user some feed back. LSurface *Src = SourceImg; LSurface *Dst = DisplayImg; for (int y=d.y1; y<=d.y2; y++) { int sy = y * Scale; int sx = d.x1 * Scale; for (int x=d.x1; x<=d.x2; x++, sx+=Scale) { COLOUR c = Src->Get(sx, sy); Dst->Colour(c); Dst->Set(x, y); } } } LayoutDirty = true; this->d->InvalidateDoc(NULL); } int LRichTextPriv::ImageBlock::GetThreadHandle() { if (ThreadHnd == 0) { ImageLoader *il = new ImageLoader(this); if (il != NULL) ThreadHnd = il->GetHandle(); } return ThreadHnd; } void LRichTextPriv::ImageBlock::UpdateDisplayImg() { if (!SourceImg) return; Size.x = SourceImg->X(); Size.y = SourceImg->Y(); int ViewX = d->Areas[LRichTextEdit::ContentArea].X(); if (ViewX > 0) { int MaxX = (int) (ViewX * 0.9); if (SourceImg->X() > MaxX) { double Ratio = (double)SourceImg->X() / MAX(1, MaxX); Scale = (int)ceil(Ratio); Size.x = (int)ceil((double)SourceImg->X() / Scale); Size.y = (int)ceil((double)SourceImg->Y() / Scale); if (DisplayImg.Reset(new LMemDC(Size.x, Size.y, SourceImg->GetColourSpace()))) { DisplayImg->Colour(L_MED); DisplayImg->Rectangle(); if (PostThreadEvent(GetThreadHandle(), M_IMAGE_RESAMPLE, (LMessage::Param)DisplayImg.Get(), (LMessage::Param)SourceImg.Get())) { UpdateThreadBusy(_FL, 1); } } } } } void LRichTextPriv::ImageBlock::UpdateThreadBusy(const char *File, int Line, int Off) { if (ThreadBusy + Off >= 0) { ThreadBusy += Off; #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - ThreadBusy=%i\n", File, Line, ThreadBusy); #endif } else { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Error: ThreadBusy=%i\n", File, Line, ThreadBusy, ThreadBusy + Off); #endif LAssert(0); } } LMessage::Result LRichTextPriv::ImageBlock::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_COMMAND: { if (!SourceImg) break; if (Msg->A() >= IDM_SCALE_IMAGE && Msg->A() < IDM_SCALE_IMAGE + CountOf(ImgScales)) { int i = (int)Msg->A() - IDM_SCALE_IMAGE; if (i >= 0 && i < (int)Scales.Length()) { ScaleInf &si = Scales[i]; ResizeIdx = i; #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Posting M_IMAGE_COMPRESS\n", _FL); #endif if (PostThreadEvent(GetThreadHandle(), M_IMAGE_COMPRESS, (LMessage::Param)SourceImg.Get(), (LMessage::Param)&si)) UpdateThreadBusy(_FL, 1); else LAssert(!"PostThreadEvent failed."); } } else switch (Msg->A()) { case IDM_CLOCKWISE: #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Posting M_IMAGE_ROTATE\n", _FL); #endif if (PostThreadEvent(GetThreadHandle(), M_IMAGE_ROTATE, (LMessage::Param) SourceImg.Get(), 1)) UpdateThreadBusy(_FL, 1); break; case IDM_ANTI_CLOCKWISE: #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Posting M_IMAGE_ROTATE\n", _FL); #endif if (PostThreadEvent(GetThreadHandle(), M_IMAGE_ROTATE, (LMessage::Param) SourceImg.Get(), -1)) UpdateThreadBusy(_FL, 1); break; case IDM_X_FLIP: #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Posting M_IMAGE_FLIP\n", _FL); #endif if (PostThreadEvent(GetThreadHandle(), M_IMAGE_FLIP, (LMessage::Param) SourceImg.Get(), 1)) UpdateThreadBusy(_FL, 1); break; case IDM_Y_FLIP: #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Posting M_IMAGE_FLIP\n", _FL); #endif if (PostThreadEvent(GetThreadHandle(), M_IMAGE_FLIP, (LMessage::Param) SourceImg.Get(), 0)) UpdateThreadBusy(_FL, 1); break; } break; } case M_IMAGE_COMPRESS: { LAutoPtr Jpg((LMemStream*)Msg->A()); ScaleInf *Si = (ScaleInf*)Msg->B(); if (!Jpg || !Si) { LAssert(0); #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Error: M_IMAGE_COMPRESS bad arg\n", _FL); #endif break; } #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Received M_IMAGE_COMPRESS\n", _FL); #endif Si->Compressed.Reset(Jpg.Release()); Si->MimeType = "image/jpeg"; UpdateThreadBusy(_FL, -1); // Change the doc to dirty d->Dirty = true; d->View->SendNotify(LNotifyDocChanged); break; } case M_IMAGE_ERROR: { LAutoPtr ErrMsg((LString*) Msg->A()); #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Received M_IMAGE_ERROR, posting M_CLOSE\n", _FL); #endif UpdateThreadBusy(_FL, -1); break; } case M_IMAGE_COMPONENT_MISSING: { LAutoPtr Component((LString*) Msg->A()); #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Received M_IMAGE_COMPONENT_MISSING, posting M_CLOSE\n", _FL); #endif UpdateThreadBusy(_FL, -1); if (Component) { auto t = LString(*Component).SplitDelimit(","); for (int i=0; iView->NeedsCapability(t[i]); } else LAssert(!"Missing component name."); break; } case M_IMAGE_SET_SURFACE: { LAutoPtr File((LStream*)Msg->B()); #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Received M_IMAGE_SET_SURFACE\n", _FL); #endif if (SourceImg.Reset((LSurface*)Msg->A())) { Scales.Length(CountOf(ImgScales)); for (int i=0; iX() * ImgScales[i] / 100; si.Sz.y = SourceImg->Y() * ImgScales[i] / 100; si.Percent = ImgScales[i]; if (si.Sz.x == SourceImg->X() && si.Sz.y == SourceImg->Y()) { ResizeIdx = i; si.Compressed.Reset(File.Release()); if (StreamMimeType) { si.MimeType = StreamMimeType; } else if (FileMimeType) { si.MimeType = FileMimeType.Get(); FileMimeType.Empty(); } } } UpdateDisplayImg(); } break; } case M_IMAGE_PROGRESS: { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Received M_IMAGE_PROGRESS\n", _FL); #endif UpdateDisplay((int)Msg->A()); break; } case M_IMAGE_FINISHED: { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Received M_IMAGE_FINISHED\n", _FL); #endif UpdateThreadBusy(_FL, -1); if (SourceImg) { UpdateDisplay(SourceImg->Y()-1); if ( DisplayImg != NULL && PostThreadEvent(GetThreadHandle(), M_IMAGE_RESAMPLE, (LMessage::Param)DisplayImg.Get(), (LMessage::Param)SourceImg.Get()) ) { UpdateThreadBusy(_FL, 1); } } break; } case M_IMAGE_RESAMPLE: { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Received M_IMAGE_RESAMPLE\n", _FL); #endif LayoutDirty = true; UpdateThreadBusy(_FL, -1); d->InvalidateDoc(NULL); SourceValid.ZOff(-1, -1); break; } case M_IMAGE_ROTATE: case M_IMAGE_FLIP: { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Received %s\n", _FL, Msg->Msg()==M_IMAGE_ROTATE?"M_IMAGE_ROTATE":"M_IMAGE_FLIP"); #endif LAutoPtr Img = SourceImg; UpdateThreadBusy(_FL, -1); SetImage(Img); break; } default: return false; } return true; } bool LRichTextPriv::ImageBlock::AddText(Transaction *Trans, ssize_t AtOffset, const uint32_t *Str, ssize_t Chars, LNamedStyle *Style) { // Can't add text to image block return false; } bool LRichTextPriv::ImageBlock::ChangeStyle(Transaction *Trans, ssize_t Offset, ssize_t Chars, LCss *Style, bool Add) { // No styles to change... return false; } ssize_t LRichTextPriv::ImageBlock::DeleteAt(Transaction *Trans, ssize_t BlkOffset, ssize_t Chars, LArray *DeletedText) { // The image is one "character" IsDeleted = BlkOffset == 0; if (IsDeleted) return true; return false; } bool LRichTextPriv::ImageBlock::DoCase(Transaction *Trans, ssize_t StartIdx, ssize_t Chars, bool Upper) { // No text to change case... return false; } #ifdef _DEBUG void LRichTextPriv::ImageBlock::DumpNodes(LTreeItem *Ti) { LString s; s.Printf("ImageBlock style=%s", Style?Style->Name.Get():NULL); Ti->SetText(s); } #endif diff --git a/test/UnitTests/UnitTests.vcxproj b/test/UnitTests/UnitTests.vcxproj --- a/test/UnitTests/UnitTests.vcxproj +++ b/test/UnitTests/UnitTests.vcxproj @@ -1,275 +1,277 @@  Debug Win32 Debug x64 Release Win32 Release x64 {18BF30E1-D77B-496E-8761-99A426DD3B41} 10.0 Application v142 false MultiByte Application v142 false MultiByte Application v142 false MultiByte Application v142 false MultiByte <_ProjectFileVersion>12.0.30501.0 $(Platform)$(Configuration)14\ $(Platform)$(Configuration)14\ true true $(Platform)$(Configuration)14\ $(Platform)$(Configuration)14\ $(Platform)$(Configuration)14\ $(Platform)$(Configuration)14\ false false $(Platform)$(Configuration)14\ $(Platform)$(Configuration)14\ .\Debug/UnitTests.tlb Disabled ..\..\include;..\..\include\lgi\win;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_CONSOLE;WINDOWS;LGI_UNIT_TESTS;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL true $(IntDir)/UnitTests.pch $(IntDir) $(IntDir) $(IntDir)vc$(PlatformToolsetVersion).pdb true ProgramDatabase _DEBUG;%(PreprocessorDefinitions) 0x0c09 $(OutDir)$(TargetName)$(TargetExt) true true $(IntDir)/UnitTests.pdb Console false MachineX86 true .\Debug/UnitTests.bsc .\Debug/UnitTests.tlb Disabled ..\..\include;..\..\include\lgi\win;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_CONSOLE;WINDOWS;LGI_UNIT_TESTS;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebugDLL true $(IntDir)/UnitTests.pch $(IntDir) $(IntDir) $(IntDir)vc$(PlatformToolsetVersion).pdb true ProgramDatabase Level3 _DEBUG;%(PreprocessorDefinitions) 0x0c09 true true $(IntDir)/UnitTests.pdb Console false true .\Debug/UnitTests.bsc .\Release/UnitTests.tlb MinSpace OnlyExplicitInline ..\..\include;..\..\include\lgi\win;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_CONSOLE;WINDOWS;LGI_UNIT_TESTS;%(PreprocessorDefinitions) true MultiThreadedDLL true true $(IntDir)/UnitTests.pch $(IntDir) $(IntDir) $(IntDir)vc$(PlatformToolsetVersion).pdb true NDEBUG;%(PreprocessorDefinitions) 0x0c09 $(OutDir)$(TargetName)$(TargetExt) true $(IntDir)/UnitTests.pdb Console false MachineX86 true .\Release/UnitTests.bsc .\Release/UnitTests.tlb MinSpace OnlyExplicitInline ..\..\include;..\..\include\lgi\win;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_CONSOLE;WINDOWS;LGI_UNIT_TESTS;%(PreprocessorDefinitions) true MultiThreadedDLL true true $(IntDir)/UnitTests.pch $(IntDir) $(IntDir) $(IntDir)vc$(PlatformToolsetVersion).pdb true NDEBUG;%(PreprocessorDefinitions) 0x0c09 $(OutDir)$(TargetName)$(TargetExt) true $(IntDir)/UnitTests.pdb Console false true .\Release/UnitTests.bsc + + - + {95df9ca4-6d37-4a85-a648-80c2712e0da1} \ No newline at end of file diff --git a/test/UnitTests/UnitTests.vcxproj.filters b/test/UnitTests/UnitTests.vcxproj.filters --- a/test/UnitTests/UnitTests.vcxproj.filters +++ b/test/UnitTests/UnitTests.vcxproj.filters @@ -1,68 +1,74 @@  {1b5d1f69-ef66-4844-baa5-25ec23d2151c} cpp;c;cxx;rc;def;r;odl;idl;hpj;bat - - {c4556f63-9655-4afe-ba0f-66090ce60a11} - {a734023f-ceb2-4882-9efc-5719c6bf4c8d} h;hpp;hxx;hm;inl {134d3911-e459-4919-8af8-82198f95a179} + + {8b34f4e6-17d2-42af-8c12-1386ba1acae0} + - - Source Files - Source Files Source Files Source Files Source Files Source Files - - Source Files - Source Files Source Files Source Files + Lgi + + + Source Files\Testing + + Source Files + + Source Files + + + Lgi + - - Header Files - Header Files Header Files Header Files + + Source Files\Testing + \ No newline at end of file diff --git a/test/UnitTests/src/BitsTest.cpp b/test/UnitTests/src/BitsTest.cpp --- a/test/UnitTests/src/BitsTest.cpp +++ b/test/UnitTests/src/BitsTest.cpp @@ -1,29 +1,37 @@ #include "lgi/common/Lgi.h" #include "UnitTests.h" #include "lgi/common/Bits.h" +LBitsTest::LBitsTest() : UnitTest("LBitsTest") +{ +} + +LBitsTest::~LBitsTest() +{ +} + bool LBitsTest::Run() { uint8_t Data[] = {0xf8, 0xcc, 0x1d, 0x00}; LBits<> Bits(Data, sizeof(Data)); if (Bits.Read(5) != 0x1f) return false; if (Bits.Read(5) != 3) return false; if (Bits.Read(7) != 0x18) return false; if (Bits.Read(3) != 1) return false; if (Bits.Read(4) != 0xd) return false; if (Bits.Length() != 1) return false; return true; } diff --git a/test/UnitTests/src/ContainerTests.cpp b/test/UnitTests/src/ContainerTests.cpp --- a/test/UnitTests/src/ContainerTests.cpp +++ b/test/UnitTests/src/ContainerTests.cpp @@ -1,265 +1,265 @@ #include "lgi/common/Lgi.h" #include "UnitTests.h" #include "lgi/common/UnrolledList.h" #include "lgi/common/HashTable.h" -class LContainersPriv +class PrivLContainers { public: bool ListDeleteOne(int pos, int sz) { LArray a; List l; for (int i=0; i a; List l; for (int i=0; i a; a.Add(1); a.Add(4); a.Add(5); a.Add(8); int n = 0; for (auto &i : a) { if (i != ref[n++]) return FAIL(_FL, "iterator error"); } } { List a; a.Add(new int(1)); a.Add(new int(4)); a.Add(new int(5)); a.Add(new int(8)); int n = 0; for (auto i : a) { if (*i != ref[n++]) return FAIL(_FL, "iterator error"); } if (n != 4) return FAIL(_FL, "count error"); n = 0; auto i3 = a.begin(); for (int *v = *i3; i3; v = *++i3) { if (*a[n] != **i3) return FAIL(_FL, "iterator error"); n++; } if (n != 4) return FAIL(_FL, "count error"); if (!d->ListInsert(45, 80)) return FAIL(_FL, "list insert"); if (!d->ListInsert(63, 80)) return FAIL(_FL, "list insert"); if (!d->ListInsert(64, 80)) return FAIL(_FL, "list insert"); if (!d->ListDeleteOne(45, 80)) return FAIL(_FL, "list delete one"); if (!d->ListDeleteOne(63, 80)) return FAIL(_FL, "list delete one"); if (!d->ListDeleteOne(64, 80)) return FAIL(_FL, "list delete one"); } #if 0 { GHashTbl h; for (int i=1; i<=100; i++) { h.Add(i, i << 1); } int count = 0; for (auto i : h) { if (i.key << 1 != i.value) return FAIL(_FL, "hash iterate"); count++; } if (count != h.Length()) return FAIL(_FL, "hash iterate"); count = 0; for (auto i : h) { if (i.key == 50) h.Delete(i.key); else count++; } if (count != h.Length()) return FAIL(_FL, "hash delete during iterate"); } #endif { LUnrolledList IntLst; IntLst.Add(123); LUnrolledList StrLst; StrLst.Add("Hello"); for (auto i : StrLst) { if (_stricmp(i, "Hello") != 0) return FAIL(_FL, "str lst iteration"); } StrLst.Add("Aacc"); StrLst.Add("Tyertw"); StrLst.Add("Bbbsd"); StrLst.Sort ( [](LString &a, LString &b, int Dir) { return _stricmp(a, b) * Dir; }, 1 ); StrLst.Delete("Tyertw"); // Compact test LUnrolledList Comp; for (int i=0; i<20; i++) Comp.Add(i+1); for (int i=0; i<8; i++) Comp.DeleteAt(8); Comp.Compact(); } { LHashTbl,int> IntMap; IntMap.Add(45, 67); for (auto i : IntMap) { printf("Int: %i -> %i\n", i.key, i.value); } IntMap.Delete(45); LHashTbl,int> CaseSenMap; CaseSenMap.Add("Abc", 34); CaseSenMap.Add("abc", 23); if (CaseSenMap.Length() != 2) return FAIL(_FL, "Wrong len."); for (auto i : CaseSenMap) { printf("Case: %s -> %i\n", i.key, i.value); } LHashTbl,int> CaseInsenMap; CaseInsenMap.Add("Abc", 34); CaseInsenMap.Add("abc", 23); if (CaseInsenMap.Length() != 1) return FAIL(_FL, "Wrong len."); for (auto i : CaseInsenMap) { printf("Insensitive: %s -> %i\n", i.key, i.value); } LHashTbl,int> ConstWideMap; ConstWideMap.Add(L"Asd", 456); for (auto i : ConstWideMap) { printf("ConstWide: %S -> %i\n", i.key, i.value); } LHashTbl,int> PoolMap; PoolMap.Add("Asd", 123); PoolMap.Add("Def", 124); for (auto i : PoolMap) { printf("PoolMap: %s -> %i\n", i.key, i.value); } // Check iterator invalidation LHashTbl,int> ItMap(4); ItMap.Add(23, 34); ItMap.Add(24, 56); int n = 0; for (auto it = ItMap.begin(); it != ItMap.end(); it++) { ItMap.Add(35, 567); n++; } if (n != 1) return FAIL(_FL, "Iterator didn't get invalidated"); // Check swapping works LHashTbl,int> SwapTest; SwapTest.Swap(ItMap); if (SwapTest.GetSize() != 8) return FAIL(_FL, "Swap failed."); } return true; } diff --git a/test/UnitTests/src/CssTest.cpp b/test/UnitTests/src/CssTest.cpp --- a/test/UnitTests/src/CssTest.cpp +++ b/test/UnitTests/src/CssTest.cpp @@ -1,170 +1,170 @@ #include "lgi/common/Lgi.h" #include "UnitTests.h" #include "lgi/common/Css.h" -class LCssTestPriv +class PrivLCssTest { public: LCss c; bool Error(char *Fmt, ...) { va_list Args; va_start(Args, Fmt); vprintf(Fmt, Args); va_end(Args); return false; } bool Parse(const char *In) { const char *Css = In; if (!c.Parse(Css)) return Error("Error: Parse error:\n%s\n", In); return true; } bool LenIs(LCss::Len l, float val, int type) { return l.Type == type && l.Value == val; } bool Test1() { char *s = "display: none;"; if (Parse(s)) { LCss::DisplayType dt = c.Display(); if (dt != LCss::DispNone) return Error("Error: Value not set %s:%i\n", _FL); } s = "display: inline;"; if (Parse(s) && c.Display() != LCss::DispInline) return Error("Error: Value not set %s:%i\n", _FL); s = "display: none;" "display: block;"; if (Parse(s) && c.Display() != LCss::DispBlock) return Error("Error: Value not set %s:%i\n", _FL); s = "position: static"; if (Parse(s) && c.Position() != LCss::PosStatic) return Error("Error: Value not set %s:%i\n", _FL); s = "position: absolute;"; if (Parse(s) && c.Position() != LCss::PosAbsolute) return Error("Error: Value not set %s:%i\n", _FL); if (Parse(s = "top: 10%;")) { LCss::Len t = c.Top(); if (t.Type != LCss::LenPercent || t.Value != 10.0) return Error("Error: Value not set %s:%i\n", _FL); } if (Parse(s = "right: 15pt;")) { LCss::Len t = c.Right(); if (t.Type != LCss::LenPt || t.Value != 15.0) return Error("Error: Value not set %s:%i\n", _FL); } if (Parse(s = "bottom: 20px;")) { LCss::Len t = c.Bottom(); if (t.Type != LCss::LenPx || t.Value != 20.0) return Error("Error: Value not set %s:%i\n", _FL); } if (Parse(s = "left: 4em;")) { LCss::Len t = c.Left(); if (t.Type != LCss::LenEm || t.Value != 4.0) return Error("Error: Value not set %s:%i\n", _FL); } if (Parse(s = "float: left;") && c.Float() != LCss::FloatLeft) { return Error("Error: Value not set %s:%i\n", _FL); } if (Parse(s = "z-index: 3;") && c.ZIndex().Value != 3.0) { return Error("Error: Value not set %s:%i\n", _FL); } return true; } bool Test2() { char *s; c.Empty(); if (Parse(s = "width: 50%;" "min-width: 10pt;" "max-width: 800px;" "height: 30px;" "min-height: 10px;" "max-height: 60px;")) { if (!LenIs(c.Width(), 50, LCss::LenPercent)) return Error("Error %s:%i\n", _FL); if (!LenIs(c.MinWidth(), 10, LCss::LenPt)) return Error("Error %s:%i\n", _FL); if (!LenIs(c.MaxWidth(), 800, LCss::LenPx)) return Error("Error %s:%i\n", _FL); if (!LenIs(c.Height(), 30, LCss::LenPx)) return Error("Error %s:%in", _FL); if (!LenIs(c.MinHeight(), 10, LCss::LenPx)) return Error("Error %s:%i\n", _FL); if (!LenIs(c.MaxHeight(), 60, LCss::LenPx)) return Error("Error %s:%i\n", _FL); } c.Empty(); if (Parse(s = "font-family: verdana, tahoma, sans;\n" "font-size: 16px;\n" "font-weight: bold;\n")) { char *f; LCss::StringsDef s; s = c.FontFamily(); if (s.Length() != 3) return Error("Not the right number of font families.", _FL); if (stricmp(f = s[0], "verdana")) return Error("Wrong font family.", _FL); if (stricmp(f = s[1], "tahoma")) return Error("Wrong font family.", _FL); if (stricmp(f = s[2], "sans")) return Error("Wrong font family.", _FL); } LCss c2; c2 = c; if (c != c2) return Error("Compare failed.\n", _FL); return true; } }; LCssTest::LCssTest() : UnitTest("LCssTest") { - d = new LCssTestPriv; + d = new PrivLCssTest; } LCssTest::~LCssTest() { DeleteObj(d); } bool LCssTest::Run() { return d->Test1() && d->Test2(); } \ No newline at end of file diff --git a/test/UnitTests/src/MatrixTest.cpp b/test/UnitTests/src/MatrixTest.cpp --- a/test/UnitTests/src/MatrixTest.cpp +++ b/test/UnitTests/src/MatrixTest.cpp @@ -1,77 +1,77 @@ #include "lgi/common/Lgi.h" #include "UnitTests.h" #include "lgi/common/Matrix.h" -class LMatrixTestPriv +class PrivLMatrixTest { public: bool Error(char *Fmt, ...) { va_list Args; va_start(Args, Fmt); vprintf(Fmt, Args); va_end(Args); return false; } bool Test1() { // Identity testing GMatrix m1; m1.SetIdentity(); for (int y=0; y a; a.SetStr( "1 2 3\n" "4 5 6\n"); GMatrix b; b.SetStr( "7 8\n" "9 10\n" "11 12\n"); GMatrix c, r; c = a * b; r.SetStr( "58 64\n" "139 154\n"); if (c != r) return false; // Inverse test GMatrix m2, m3, m4; m2.SetStr( "1 5 2\n" "1 1 7\n" "0 -3 4\n"); m3 = m2; if (!m3.Inverse()) return false; m4 = m2 * m3; if (!m4.IsIdentity()) return false; return true; } }; LMatrixTest::LMatrixTest() : UnitTest("LMatrixTest") { - d = new LMatrixTestPriv; + d = new PrivLMatrixTest; } LMatrixTest::~LMatrixTest() { DeleteObj(d); } bool LMatrixTest::Run() { return d->Test1(); } \ No newline at end of file diff --git a/test/UnitTests/src/NetworkTests.cpp b/test/UnitTests/src/NetworkTests.cpp new file mode 100644 --- /dev/null +++ b/test/UnitTests/src/NetworkTests.cpp @@ -0,0 +1,39 @@ +#include "lgi/common/Lgi.h" +#include "lgi/common/Net.h" +#include "UnitTests.h" + +class PrivNetworkTests +{ +public: +}; + +NetworkTests::NetworkTests() : UnitTest("NetworkTests") +{ + d = new PrivNetworkTests; +} + +NetworkTests::~NetworkTests() +{ + DeleteObj(d); +} + +bool NetworkTests::Run() +{ + /* + + Things to test: + + LgiFunc char *InetGetHeaderField(const char *Headers, const char *Field, ssize_t Len = -1); + LgiExtern LString LGetHeaderField(LString Headers, const char *Field); + LgiFunc char *InetGetSubField(const char *s, const char *Field); + LgiExtern LString LGetSubField(LString HeaderValue, const char *Field); + + LUri + + + */ + + + + return true; +} diff --git a/test/UnitTests/src/RangeTest.cpp b/test/UnitTests/src/RangeTest.cpp --- a/test/UnitTests/src/RangeTest.cpp +++ b/test/UnitTests/src/RangeTest.cpp @@ -1,104 +1,112 @@ #include "lgi/common/Lgi.h" #include "UnitTests.h" #include "lgi/common/UnrolledList.h" #include "lgi/common/HashTable.h" +LRangeTest::LRangeTest() : UnitTest("LRangeTest") +{ +} + +LRangeTest::~LRangeTest() +{ +} + bool LRangeTest::Run() { LRange a, b, c; // Overlap tests: b.Set(20, 10); a.Set(10, 5); if (a.Overlap(b).Valid()) return FAIL(_FL, "overlap"); a.Set(10, 10); if (a.Overlap(b).Valid()) return FAIL(_FL, "overlap"); a.Set(10, 11); if (!a.Overlap(b).Valid()) return FAIL(_FL, "overlap"); a.Set(22, 3); if (!a.Overlap(b).Valid()) return FAIL(_FL, "overlap"); a.Set(30, 5); if (a.Overlap(b).Valid()) return FAIL(_FL, "overlap"); a.Set(28, 5); if (!a.Overlap(b).Valid()) return FAIL(_FL, "overlap"); a.Set(33, 5); if (a.Overlap(b).Valid()) return FAIL(_FL, "overlap"); /* The different -= cases we need to test: 1) a is entirely before b 2) a's end is b's start 3) a overlaps the start of b 4) a overlaps the whole of b 5) a overlaps the middle of b 6) a overlaps part of b but matching the end 7) a overlaps the end and past the end of b 8) a's start adjoins b's end 9) a is entirely after b */ // 1) a is entirely before b a.Set(10, 5); b.Set(20, 10); c = b; b -= a; if (b != c) return FAIL(_FL, "range test 1"); // 2) a's end is b's start a.Set(10, 10); b.Set(20, 10); c = b; b -= a; if (b != c) return FAIL(_FL, "range test 2"); // 3) a overlaps the start of b a.Set(12, 10); b.Set(20, 10); c = b; b -= a; if (b != LRange(12,8)) return FAIL(_FL, "range test 3"); // 4) a overlaps the whole of b a.Set(15, 30); b.Set(20, 10); c = b; b -= a; if (b.Valid()) return FAIL(_FL, "range test 3"); // 5) a overlaps the middle of b a.Set(22, 6); b.Set(20, 10); c = b; b -= a; if (b != LRange(20, 4)) return FAIL(_FL, "range test 3"); // 6) a overlaps part of b but matching the end a.Set(25, 5); b.Set(20, 10); c = b; b -= a; if (b != LRange(20, 5)) return FAIL(_FL, "range test 3"); // 7) a overlaps the end and past the end of b a.Set(25, 10); b.Set(20, 10); c = b; b -= a; if (b != LRange(20, 5)) return FAIL(_FL, "range test 3"); // 8) a's start adjoins b's end a.Set(30, 10); b.Set(20, 10); c = b; b -= a; if (b != c) return FAIL(_FL, "range test 3"); // 8) a is entirely after b a.Set(35, 10); b.Set(20, 10); c = b; b -= a; if (b != c) return FAIL(_FL, "range test 3"); return true; } diff --git a/test/UnitTests/src/StringClassTests.cpp b/test/UnitTests/src/StringClassTests.cpp --- a/test/UnitTests/src/StringClassTests.cpp +++ b/test/UnitTests/src/StringClassTests.cpp @@ -1,187 +1,187 @@ #include "lgi/common/Lgi.h" #include "UnitTests.h" #include "lgi/common/StringClass.h" #ifdef LGI_UNIT_TESTS int32 LString::RefStrCount = 0; #endif -class LStringClassTestPriv +class PrivLStringClassTest { public: }; LStringClassTest::LStringClassTest() : UnitTest("LStringClassTest") { - d = new LStringClassTestPriv; + d = new PrivLStringClassTest; } LStringClassTest::~LStringClassTest() { DeleteObj(d); } bool LStringClassTest::Run() { // Constructor test { LString a("test"); LString b = a; } if (LString::RefStrCount != 0) return FAIL(_FL, "RefStrCount mismatch"); // Assignment operator test { LString a("test"); LString b; b = a; } if (LString::RefStrCount != 0) return FAIL(_FL, "RefStrCount mismatch"); // Split/Join tests { const char *Base = "123,456,789"; LString a(Base); if (LString::RefStrCount != 1) return FAIL(_FL, "RefStrCount mismatch"); LString::Array b = a.Split(","); if (LString::RefStrCount != 4) return FAIL(_FL, "RefStrCount mismatch"); LString sep("-"); if (LString::RefStrCount != 5) return FAIL(_FL, "RefStrCount mismatch"); LString c = sep.Join(b); if (LString::RefStrCount != 6) return FAIL(_FL, "RefStrCount mismatch"); if (c.Get() && stricmp(c.Get(), "123-456-789")) return FAIL(_FL, "Joined string incorrect"); if (b[1].Int() != 456) return FAIL(_FL, "Int cast failure"); LString d(",,123,345,,"); b = d.SplitDelimit(",", -1, false); if (b.Length() != 6) return FAIL(_FL, "Wrong token count"); b = d.SplitDelimit(",", -1, true); if (b.Length() != 2) return FAIL(_FL, "Wrong token count"); if (strcmp(Base, a)) return FAIL(_FL, "Original string modified"); } if (LString::RefStrCount != 0) return FAIL(_FL, "RefStrCount mismatch"); // C string assignment / cast testing { const char *cstr = "Test"; LString a; a = cstr; if (_stricmp(cstr, a)) return FAIL(_FL, "C String assignment failed"); if (strcmp(cstr, a)) return FAIL(_FL, "Original string modified"); } if (LString::RefStrCount != 0) return FAIL(_FL, "RefStrCount mismatch"); // Upper / lower case conversion { const char *cstr = "Test"; LString a = cstr, b; b = a.Lower(); if (strcmp(a, "Test") != 0 || strcmp(b, "test")) return FAIL(_FL, "Lower failed"); b = a.Upper(); if (strcmp(a, "Test") != 0 || strcmp(b, "TEST")) return FAIL(_FL, "Upper failed"); } if (LString::RefStrCount != 0) return FAIL(_FL, "RefStrCount mismatch"); // Find testing { LString a("This is a test string"); LString b; // Empty if (a.Find("asdasdwer") >= 0) return FAIL(_FL, "Find failed"); if (a.Find(NULL) >= 0) return FAIL(_FL, "Find failed"); if (b.Find(NULL) >= 0) return FAIL(_FL, "Find failed"); if (b.Find("Asdwegewr") >= 0) return FAIL(_FL, "Find failed"); if (a.Find("This") != 0) return FAIL(_FL, "Find failed"); if (a.Find("test") != 10) return FAIL(_FL, "Find failed"); if (a.Find("string") != 15) return FAIL(_FL, "Find failed"); if (a.Find("is") != 2) return FAIL(_FL, "Find failed"); if (a.RFind("is") != 5) return FAIL(_FL, "RFind failed"); if (a.RFind("string") != 15) return FAIL(_FL, "RFind failed"); if (a.RFind(NULL) >= 0) return FAIL(_FL, "RFind failed"); } if (LString::RefStrCount != 0) return FAIL(_FL, "RefStrCount mismatch"); // Substring testing { const char *Base = "This is a test string"; LString a(Base); int b; b = a(5); if (b != 'i') return FAIL(_FL, "Char substring failed"); b = a(-1); if (b != 'g') return FAIL(_FL, "Char substring failed"); LString c; c = a(5, 7); if (_stricmp(c, "is")) return FAIL(_FL, "Range substring failed"); if (strcmp(Base, a)) return FAIL(_FL, "Original string modified"); } if (LString::RefStrCount != 0) return FAIL(_FL, "RefStrCount mismatch"); // Whitespace strip testing { const char *Base = " \ta test. \t\r\n"; LString a(Base); if (_stricmp(a.Strip(), "a test.")) return FAIL(_FL, "Strip whitespace failed."); if (_stricmp(a.LStrip(), "a test. \t\r\n")) return FAIL(_FL, "Strip whitespace failed."); if (_stricmp(a.RStrip(), " \ta test.")) return FAIL(_FL, "Strip whitespace failed."); if (strcmp(Base, a)) return FAIL(_FL, "Original string modified"); } if (LString::RefStrCount != 0) return FAIL(_FL, "RefStrCount mismatch"); // Concatenation tests { const char *Base1 = "This is the first part. "; const char *Base2 = "This is the 2nd part."; LString a(Base1); LString b(Base2); LString c; c = a + b; if (strcmp(c, "This is the first part. This is the 2nd part.")) return FAIL(_FL, "Concat string incorrect"); LString d(Base1); d += b; if (strcmp(d, "This is the first part. This is the 2nd part.")) return FAIL(_FL, "Concat string incorrect"); if (strcmp(Base1, a)) return FAIL(_FL, "Original string modified"); if (strcmp(Base2, b)) return FAIL(_FL, "Original string modified"); } if (LString::RefStrCount != 0) return FAIL(_FL, "RefStrCount mismatch"); return true; } diff --git a/test/UnitTests/src/StringPipeTests.cpp b/test/UnitTests/src/StringTests.cpp rename from test/UnitTests/src/StringPipeTests.cpp rename to test/UnitTests/src/StringTests.cpp --- a/test/UnitTests/src/StringPipeTests.cpp +++ b/test/UnitTests/src/StringTests.cpp @@ -1,24 +1,65 @@ #include "lgi/common/Lgi.h" +#include "lgi/common/TextConvert.h" + #include "UnitTests.h" -class LStringPipeTestPriv +class PrivLStringTests { public: }; -LStringPipeTest::LStringPipeTest() : UnitTest("LStringPipeTest") +LStringTests::LStringTests() : UnitTest("LStringPipeTest") { - d = new LStringPipeTestPriv; + d = new PrivLStringTests; } -LStringPipeTest::~LStringPipeTest() +LStringTests::~LStringTests() { DeleteObj(d); } -bool LStringPipeTest::Run() +bool LStringTests::Run() { + /* Things to test: + + bool Is8Bit(const char *Text); + + [[deprecated]] char *DecodeBase64Str(char *Str, ssize_t Len = -1); + LString LDecodeBase64Str(LString Str); + + [[deprecated]] char *DecodeQuotedPrintableStr(char *Str, ssize_t Len = -1); + LString LDecodeQuotedPrintableStr(LString Str); + + [[deprecated]] char *DecodeRfc2047(char *Str); + LString LDecodeRfc2047(LString Str); + + LString LEncodeRfc2047(LString Str, const char *Charset, List *CharsetPrefs, ssize_t LineLength = 0); + + */ + + const char *EncodeResult1 = "=?utf-8?B?QmV5dHVsbGFoIEdlbsOn?="; + const char *EncodeResult2 = "=?iso-8859-9?Q?Beytullah_Gen=E7?="; + + List CharsetPrefs; + const char *Rfc2047Input = "Beytullah Genç"; + const char *Charset = "windows-1252"; + + #if 0 + LAutoString result1( EncodeRfc2047(NewStr(Rfc2047Input), Charset, &CharsetPrefs)); + if (Stricmp(result1.Get(), EncodeResult1)) + return FAIL(_FL, "EncodeRfc2047"); + LString result2 = LEncodeRfc2047(Rfc2047Input, Charset, &CharsetPrefs); + if (Stricmp(result2.Get(), EncodeResult1)) + return FAIL(_FL, "LEncodeRfc2047"); + #endif + + CharsetPrefs.Add("iso-8859-9"); + + LAutoString result3( EncodeRfc2047(NewStr(Rfc2047Input), Charset, &CharsetPrefs)); + LString result4 = LEncodeRfc2047(Rfc2047Input, Charset, &CharsetPrefs); return true; } + + diff --git a/test/UnitTests/src/UnitTests.cpp b/test/UnitTests/src/UnitTests.cpp --- a/test/UnitTests/src/UnitTests.cpp +++ b/test/UnitTests/src/UnitTests.cpp @@ -1,46 +1,47 @@ #include "lgi/common/Lgi.h" #include "UnitTests.h" void UnitTests() { LArray Tests; Tests.Add(new LRangeTest); Tests.Add(new LContainers); Tests.Add(new LAutoPtrTest); Tests.Add(new LCssTest); Tests.Add(new LMatrixTest); Tests.Add(new LStringClassTest); Tests.Add(new LDateTimeTest); - Tests.Add(new LStringPipeTest); + Tests.Add(new LStringTests); Tests.Add(new LBitsTest); Tests.Add(new JsonTest); + Tests.Add(new NetworkTests); for (int i=0; iRun(); if (b) { printf("Passed: %s\n", Tests[i]->Name()); } else { printf("Error: %s test failed.\n", Tests[i]->Name()); break; } } Tests.DeleteObjects(); } int main(int args, const char **arg) { OsAppArguments Args(args, arg); LApp a(Args, "LgiUnitTests"); if (a.IsOk()) { UnitTests(); } LSleep(2000); return 0; } \ No newline at end of file diff --git a/test/UnitTests/src/UnitTests.h b/test/UnitTests/src/UnitTests.h --- a/test/UnitTests/src/UnitTests.h +++ b/test/UnitTests/src/UnitTests.h @@ -1,132 +1,57 @@ -#ifndef _UNIT_TESTS_H_ -#define _UNIT_TESTS_H_ +#pragma once class UnitTest : public LBase { public: UnitTest(const char *name) { Name(name); } virtual ~UnitTest() {} virtual bool Run() = 0; bool FAIL(char *File, int Line, char *Msg) { LAssert(0); printf("%s:%i - Error in %s: %s\n", File, Line, Name(), Msg); return false; } }; -class LAutoPtrTest : public UnitTest -{ - class LAutoPtrTestPriv *d; - -public: - LAutoPtrTest(); - ~LAutoPtrTest(); - - bool Run(); -}; - -class LCssTest : public UnitTest -{ - class LCssTestPriv *d; +#define DECL_TEST(name) \ + class name : public UnitTest \ + { \ + class Priv##name *d; \ + public: \ + name(); \ + ~name(); \ + bool Run(); \ + } -public: - LCssTest(); - ~LCssTest(); - - bool Run(); -}; - -class LMatrixTest : public UnitTest -{ - class LMatrixTestPriv *d; - -public: - LMatrixTest(); - ~LMatrixTest(); - - bool Run(); -}; -class LContainers : public UnitTest -{ - class LContainersPriv *d; - -public: - LContainers(); - ~LContainers(); - - bool Run(); -}; - -class LStringClassTest : public UnitTest -{ - class LStringClassTestPriv *d; - -public: - LStringClassTest(); - ~LStringClassTest(); - - bool Run(); -}; - -class LStringPipeTest : public UnitTest -{ - class LStringPipeTestPriv *d; - -public: - LStringPipeTest(); - ~LStringPipeTest(); - - bool Run(); -}; +DECL_TEST(LAutoPtrTest); +DECL_TEST(LCssTest); +DECL_TEST(LMatrixTest); +DECL_TEST(LContainers); +DECL_TEST(LStringClassTest); +DECL_TEST(LStringTests); +DECL_TEST(LRangeTest); +DECL_TEST(JsonTest); +DECL_TEST(LBitsTest); +DECL_TEST(NetworkTests); class LDateTimeTest : public UnitTest { public: LDateTimeTest() : UnitTest("LDateTimeTest") {} - ~LDateTimeTest() {} - + bool Run() { #ifdef _DEBUG return LDateTime_Test(); #else #error "Requires _DEBUG" #endif } -}; - -class LRangeTest : public UnitTest -{ -public: - LRangeTest() : UnitTest("LRangeTest") {} - ~LRangeTest() {} - - bool Run(); -}; - -class JsonTest : public UnitTest -{ -public: - JsonTest(); - ~JsonTest(); - - bool Run(); -}; - -class LBitsTest : public UnitTest -{ -public: - LBitsTest() : UnitTest("LBitsTest") {} - ~LBitsTest() {} - - bool Run(); -}; - -#endif \ No newline at end of file +}; \ No newline at end of file