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("%s>", Tag.Get());
}
}
else if (Tag)
{
if (Text())
{
p.Write((char*)">", 1);
TextToStream(p, Text());
p.Print("%s>", 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