diff --git a/include/lgi/common/LiteHtmlView.h b/include/lgi/common/LiteHtmlView.h --- a/include/lgi/common/LiteHtmlView.h +++ b/include/lgi/common/LiteHtmlView.h @@ -1,22 +1,25 @@ #pragma once class LiteHtmlView : public LLayout { protected: struct LiteHtmlViewPriv *d; public: LiteHtmlView(int id); ~LiteHtmlView(); bool SetUrl(LString url); + // Events: + virtual void OnNavigate(LString url); + // LLayout impl void OnAttach() override; LCursor GetCursor(int x, int y) override; void OnPaint(LSurface *pDC) override; int OnNotify(LViewI *c, LNotification n) override; void OnMouseClick(LMouse &m) override; void OnMouseMove(LMouse &m) override; }; diff --git a/src/common/Gdc2/Font/FontSystem.cpp b/src/common/Gdc2/Font/FontSystem.cpp --- a/src/common/Gdc2/Font/FontSystem.cpp +++ b/src/common/Gdc2/Font/FontSystem.cpp @@ -1,767 +1,767 @@ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Library.h" #include "lgi/common/LibraryUtils.h" #if defined(LGI_STATIC) #undef HAS_ICONV #endif #define DEBUG_ICONV_LOG 0 #if HAS_ICONV // // Get 'iconv.h' from http://www.gnu.org/software/libiconv // Current download at time of writing: // http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.8.tar.gz // // Then add whatever include path to your project or development // settings. Otherwise you can build without extended charset // support by changing the HAS_ICONV define in Lgi.h to '0' // // Linux should always build with iconv, on windows you may or // may not want to bother depending on what sort of app your // writing. // #include "iconv.h" #if defined(WIN32) typedef const char IconvChar; #else typedef char IconvChar; #endif #endif ///////////////////////////////////////////////////////////////////// // Private growable class for binary compatability class LFontSystemPrivate : public LLibrary { public: bool DefaultGlyphSub; int Used, Refs; bool FontTableLoaded; bool SubSupport; bool CheckedConfig; bool LibCheck; #ifdef __GTK_H__ Gtk::PangoFontMap *Map; Gtk::PangoContext *Ctx; LFontSystemPrivate() { Map = Gtk::pango_cairo_font_map_get_default(); if (!Map) LAssert(!"pango_cairo_font_map_get_default failed.\n"); Ctx = Gtk::pango_cairo_font_map_create_context((Gtk::PangoCairoFontMap*)Map); if (!Ctx) LAssert(!"pango_cairo_font_map_create_context failed.\n"); } #endif #if HAS_ICONV #ifdef WIN32 DynFunc2(iconv_t, libiconv_open, const char*, tocode, const char*, fromcode); DynFunc5(size_t, libiconv, iconv_t, cd, IconvChar**, inbuf, size_t*, inbytesleft, char**, outbuf, size_t*, outbytesleft); DynFunc1(int, libiconv_close, iconv_t, cd); #elif !defined(MAC) // Use glibc I guess iconv_t libiconv_open(const char *tocode, const char *fromcode) { return ::iconv_open(tocode, fromcode); } size_t libiconv(iconv_t cd, IconvChar** inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft) { return ::iconv(cd, inbuf, inbytesleft, outbuf, outbytesleft); } int libiconv_close(iconv_t cd) { return ::iconv_close(cd); } bool IsLoaded() { return true; } #endif #endif // HAS_ICONV }; ///////////////////////////////////////////////////////////////////// static bool FontSystemDone = false; LFontSystem *LFontSystem::Me = 0; LFontSystem *LFontSystem::Inst() { if (!Me && !FontSystemDone) new LFontSystem; return Me; } LFontSystem::LFontSystem() { Me = this; d = new LFontSystemPrivate; // Glyph sub setup int Os = LGetOs(); d->SubSupport = (Os == LGI_OS_LINUX) || (Os == LGI_OS_WIN64) || (Os == LGI_OS_WIN32) #ifdef __GTK_H__ || (Os == LGI_OS_MAC_OS_X) #endif ; // && Rev == 0); // WinXP does it's own glyph substitution d->DefaultGlyphSub = d->SubSupport; d->CheckedConfig = false; d->FontTableLoaded = false; d->Used = 1; // the '0th' font spot isn't used // because Lut[Char] == 0 means no font // available d->Refs = 0; d->LibCheck = false; ZeroObj(Lut); // Clear the table to 'no font' ZeroObj(Font); // Initialize the list of fonts to empty } LFontSystem::~LFontSystem() { // Clean up all our resources for (int i=0; iUsed; i++) { DeleteObj(Font[i]); } DeleteObj(d); Me = 0; FontSystemDone = true; } #ifdef __GTK_H__ Gtk::PangoFontMap *LFontSystem::GetFontMap() { return d->Map; } Gtk::PangoContext *LFontSystem::GetContext() { return d->Ctx; } #endif bool LFontSystem::GetGlyphSubSupport() { return d->SubSupport; } bool LFontSystem::GetDefaultGlyphSub() { if (!d->CheckedConfig && LAppInst) { auto GlyphSub = LAppInst->GetConfig(LApp::CfgFontsGlyphSub); if (GlyphSub) d->DefaultGlyphSub = atoi(GlyphSub) != 0; d->CheckedConfig = true; } return d->DefaultGlyphSub; } void LFontSystem::SetDefaultGlyphSub(bool i) { if (d->SubSupport) d->DefaultGlyphSub = i; } #ifdef WINNATIVE int CALLBACK _EnumFonts(ENUMLOGFONT FAR *lpelf, NEWTEXTMETRIC FAR *lpntm, int FontType, LPARAM lParam) { LString::Array *p = (LString::Array*) lParam; if (p) p->New() = lpelf->elfLogFont.lfFaceName; return true; } #endif int StringSort(LString *a, LString *b) { if (a && b) return stricmp(*a, *b); return 0; } bool LFontSystem::EnumerateFonts(LString::Array &Fonts) { Fonts.SetFixedLength(false); if (!AllFonts.Length()) { #if defined WINNATIVE HDC hDC = CreateCompatibleDC(NULL); if (hDC) { EnumFontFamilies( hDC, NULL, (FONTENUMPROC) _EnumFonts, (LPARAM) &AllFonts); DeleteDC(hDC); } #elif defined __GTK_H__ Gtk::PangoFontFamily **families; int n_families; Gtk::PangoFontMap * fontmap; fontmap = Gtk::pango_cairo_font_map_get_default(); Gtk::pango_font_map_list_families (fontmap, & families, & n_families); for (int i = 0; i < n_families; i++) { Gtk::PangoFontFamily * family = families[i]; const char * family_name; family_name = Gtk::pango_font_family_get_name (family); AllFonts.New() = family_name; } Gtk::g_free (families); #elif LGI_COCOA auto avail = [[NSFontManager sharedFontManager] availableFontFamilies]; for (NSString *s in avail) { AllFonts.New() = [s UTF8String]; } #elif LGI_CARBON CFArrayRef fontFamilies = CTFontManagerCopyAvailableFontFamilyNames(); if (fontFamilies) { for(CFIndex i = 0; i < CFArrayGetCount(fontFamilies); i++) { CFStringRef fontName = (CFStringRef) CFArrayGetValueAtIndex(fontFamilies, i); if (fontName) AllFonts.New() = fontName; } CFRelease(fontFamilies); } #elif HAIKU int32 families = count_font_families(); for (int32 i=0; iLibCheck = false; } bool LFontSystem::HasIconv(bool Quiet) { if (d->IsLoaded()) return true; bool Status = false; if (!d->LibCheck) { d->LibCheck = true; #ifdef WINDOWS auto LibName = "iconv-2." LGI_LIBRARY_EXT; #else auto LibName = "libiconv." LGI_LIBRARY_EXT; #endif Status = d->Load(LibName); if (!Status && !Quiet) { if (!NeedsCapability("libiconv")) { static bool Warn = true; if (Warn) { Warn = false; LAssert(!"Iconv is not available"); } } } } return Status; } // This converts a normal charset to an Apple encoding ID #if defined LGI_CARBON static CFStringEncoding CharsetToEncoding(const char *cs) { CFStringRef InputCs = CFStringCreateWithCString(0, cs, kCFStringEncodingUTF8); if (!InputCs) return kCFStringEncodingUTF8; // Um what to do here? CFStringEncoding enc = CFStringConvertIANACharSetNameToEncoding(InputCs); CFRelease(InputCs); return enc; } #elif defined LGI_COCOA static CFStringEncoding CharsetToEncoding(const char *cs) { LString s = cs; auto r = s.CreateStringRef(); auto e = CFStringConvertIANACharSetNameToEncoding(r); CFRelease(r); return e; } #endif ssize_t LFontSystem::IconvConvert(const char *OutCs, LStreamI *Out, const char *InCs, const char *&In, ssize_t InLen) { LAssert(InLen > 0); if (!Out || !In) return 0; #if defined(MAC) char Buf[2 << 10]; CFStringEncoding InEnc = CharsetToEncoding(InCs); CFStringEncoding OutEnc = CharsetToEncoding(OutCs); if (InEnc != kCFStringEncodingInvalidId && OutEnc != kCFStringEncodingInvalidId) { CFStringRef r = CFStringCreateWithBytes(0, (const UInt8 *)In, InLen, InEnc, false); if (r) { CFRange g = { 0, CFStringGetLength(r) }; CFIndex used = 0; CFIndex ret; while ((ret = CFStringGetBytes(r, g, OutEnc, '?', false, (UInt8*)Buf, sizeof(Buf), &used)) > 0 && g.length > 0) { // char16 *b = (char16*)Buf; Out->Write(Buf, used); g.location += ret; g.length -= ret; } CFRelease(r); } else return 0; } else return 0; #elif HAS_ICONV if (!HasIconv(false)) { return 0; } char Buf[2 << 10] = {0}; iconv_t Conv; if ((NativeInt)(Conv = d->libiconv_open(OutCs, InCs)) >= 0) { char *i = (char*)In; LAssert((NativeInt)Conv != 0xffffffff); while (InLen) { char *o = (char*)Buf; size_t OutLen = sizeof(Buf); ssize_t OldInLen = InLen; size_t InSz = InLen; #if DEBUG_ICONV_LOG printf("iconv %s,%p,%i->%s,%p,%i", InCs, In, (int)InSz, OutCs, Out, (int)sizeof(Buf)); #endif ssize_t s = d->libiconv(Conv, (IconvChar**)&i, &InSz, &o, &OutLen); #if DEBUG_ICONV_LOG printf(" = %i\n", (int)s); #endif InLen = InSz; Out->Write((uchar*)Buf, sizeof(Buf) - OutLen); if (OldInLen == InLen) break; } d->libiconv_close(Conv); } else { LgiTrace("Iconv won't load.\n"); return 0; } #endif return 1; } ssize_t LFontSystem::IconvConvert(const char *OutCs, char *Out, ssize_t OutLen, const char *InCs, const char *&In, ssize_t InLen) { ssize_t Status = 0; if (!Out || !In || !HasIconv(false)) return 0; #if defined(MAC) CFStringEncoding InEnc = CharsetToEncoding(InCs); CFStringEncoding OutEnc = CharsetToEncoding(OutCs); if (InEnc != kCFStringEncodingInvalidId && OutEnc != kCFStringEncodingInvalidId) { CFStringRef r = CFStringCreateWithBytes(0, (const UInt8 *)In, InLen, InEnc, false); if (r) { CFRange g = { 0, CFStringGetLength(r) }; CFIndex ret = CFStringGetBytes(r, g, OutEnc, '?', false, (UInt8*)Out, OutLen, 0); CFRelease(r); return ret; } } #elif HAS_ICONV // Set locale yet? static bool sl = false; if (!sl) { sl = true; setlocale(LC_ALL, ""); } // Iconv conversion iconv_t Conv; if ((Conv = d->libiconv_open(OutCs, InCs)) != NULL) { size_t InSz = InLen; size_t OutSz = OutLen; char *o = Out; char *i = (char*)In; // Convert char *Start = o; #if DEBUG_ICONV_LOG printf("iconv %s,%p,%i->%s,%p,%i", InCs, In, (int)InSz, OutCs, Out, (int)OutSz); #endif ssize_t s = d->libiconv(Conv, (IconvChar**)&i, &InSz, &o, &OutSz); #if DEBUG_ICONV_LOG printf(" = %i\n", (int)s); #endif InLen = InSz; OutLen = OutSz; d->libiconv_close(Conv); In = (const char*)i; Status = o - Out; } else { LgiTrace("Iconv not present/won't load.\n"); } #endif return Status; } LFont *LFontSystem::GetBestFont(char *Str) { LFont *MatchingFont = 0; if (d->SubSupport) { char16 *s = Utf8ToWide(Str); if (s) { // Make list of possible fonts List Possibles; char16 *i; for (i = s; *i; i++) { LFont *Font = GetGlyph(*i, LSysFont); if (Font) { bool Has = false; for (auto h: Possibles) { if (h == Font) { Has = true; break; } } if (!Has) { Possibles.Insert(Font); } } } // Choose best match amongst possibles int MatchingChars = 0; for (auto h: Possibles) { int Chars = 0; for (i = s; *i; i++) { if (h->GetGlyphMap() && _HasUnicodeGlyph(h->GetGlyphMap(), *i)) { Chars++; } } if (!MatchingFont || Chars > MatchingChars) { MatchingFont = h; } } DeleteArray(s); } } return MatchingFont; } typedef LHashTbl,int> FontMap; bool LFontSystem::AddFont(LAutoPtr Fnt) { if (!Fnt) return false; if (d->Used >= CountOf(Font)) return false; Fnt->Create(); auto *Map = Fnt->GetGlyphMap(); if (Map) { uint8_t Used = d->Used; // Insert all the characters of this font into the LUT // so that we can map from a character back to the font for (int k=0; k<=MAX_UNICODE; k += 8) { // unroll the loop for maximum speed.. uint8_t m = Map[k >> 3]; #define TestLut(i) \ if (!Lut[k+i] && (m & (1 << i))) \ Lut[k+i] = Used; TestLut(0); TestLut(1); TestLut(2); TestLut(3); TestLut(4); TestLut(5); TestLut(6); TestLut(7); } } Font[d->Used++] = Fnt.Release(); return true; } LFont *LFontSystem::GetGlyph(uint32_t u, LFont *UserFont) { if (u > MAX_UNICODE || !UserFont) { LAssert(!"Invalid character"); return 0; } // Check app font if (!d->SubSupport || (UserFont->GetGlyphMap() && _HasUnicodeGlyph(UserFont->GetGlyphMap(), u))) { return UserFont; } // Check LUT LFont *Has = 0; if (Lut[u]) { Has = Font[Lut[u]]; LAssert(Has != NULL); if (!Has) { LgiTrace("%s:%i - Font table missing pointer. u=%i Lut[u]=%i\n", _FL, u, Lut[u]); Has = UserFont; } } else if (d->Used < 255 && !d->FontTableLoaded) { // Add fonts to Lut... if (!SubFonts.Length()) { #if LGI_EXCEPTIONS try { #endif FontMap Pref(0, 0); if (LFontSystem::Inst()->EnumerateFonts(SubFonts)) { // Reorder font list to prefer certain known as good fonts or // avoid certain bad fonts. if (LGetOs() == LGI_OS_WIN32 || LGetOs() == LGI_OS_WIN64) { Pref.Add("Microsoft Sans Serif", 1); Pref.Add("Arial Unicode MS", 1); Pref.Add("Verdana", 1); Pref.Add("Tahoma", 1); Pref.Add("Bookworm", -1); Pref.Add("Christmas Tree", -1); Pref.Add("MingLiU", -1); } if (LGetOs() == LGI_OS_LINUX) { // Windows fonts are much better than anything Linux // has to offer. Pref.Add("Verdana", 1); Pref.Add("Tahoma", 1); Pref.Add("Arial Unicode MS", 1); // Most linux fonts suck... and the rest aren't much // good either Pref.Add("AR PL *", -1); Pref.Add("Baekmuk *", -1); Pref.Add("console8*", -1); Pref.Add("Courier*", -1); Pref.Add("Fangsong*", -1); Pref.Add("Kochi*", -1); Pref.Add("MiscFixed", -1); Pref.Add("Serto*", -1); Pref.Add("Standard Symbols*", -1); Pref.Add("Nimbus*", -1); } // Prefer these fonts... SubFonts.Sort([Pref](auto a, auto b) { int ap = Pref.Find(*a); int bp = Pref.Find(*b); return bp - ap; }); // Delete fonts prefixed with '@' to the end, as they are for // vertical rendering... and aren't suitable for what LGI uses // fonts for. for (unsigned i=0; iUsed; while (SubFonts.Length() > 0 && (LCurrentTime() - Start) < 10) { LString f = SubFonts[0]; SubFonts.DeleteAt(0, true); if (d->Used >= CountOf(Font)) { // No more space SubFonts.Empty(); break; } LAutoPtr Fnt(new LFont); if (Fnt) { *Fnt.Get() = *UserFont; Fnt->Face(f); if (AddFont(Fnt)) { LFont *Prev = Font[d->Used - 1]; auto PrevMap = Prev->GetGlyphMap(); if (PrevMap && _HasUnicodeGlyph(Prev->GetGlyphMap(), u)) { Has = Prev; LAssert(Has != NULL); break; } } } } - LgiTrace("Loaded %i fonts for glyph sub.\n", d->Used - Used); + // LgiTrace("Loaded %i fonts for glyph sub.\n", d->Used - Used); #if LGI_EXCEPTIONS } catch (...) { LgiTrace("%s:%i - Glyph search crashed.\n", _FL); } #endif if (!SubFonts.Length()) { d->FontTableLoaded = true; } } return Has; } diff --git a/src/common/LiteHtml/LiteHtmlView.cpp b/src/common/LiteHtml/LiteHtmlView.cpp --- a/src/common/LiteHtml/LiteHtmlView.cpp +++ b/src/common/LiteHtml/LiteHtmlView.cpp @@ -1,416 +1,566 @@ #include "lgi/common/Lgi.h" #include "lgi/common/Box.h" #include "lgi/common/Edit.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Layout.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/Menu.h" #include "lgi/common/LiteHtmlView.h" +#include "lgi/common/Thread.h" +#include "lgi/common/Http.h" #undef min #undef max #include "litehtml/html.h" #define NOT_IMPL \ - printf("%s:%i - %s not impl.\n", _FL, __func__); \ + LgiTrace("%s:%i - %s not impl.\n", _FL, __func__); \ LAssert(!"not impl"); +struct LiteHtmlViewPriv : + public litehtml::document_container, + public LCancel +{ + struct NetworkThread : + public LThread, + public LCancel + { + using TCallback = std::function; + + LView *view = NULL; + LString url; + TCallback callback; -struct LiteHtmlViewPriv : - public litehtml::document_container -{ + NetworkThread(LView *View, const char *Url, TCallback cb) : + LThread("LiteHtmlViewPriv.NetworkThread") + { + view = View; + url = Url; + callback = std::move(cb); + Run(); + } + + ~NetworkThread() + { + Cancel(); + WaitForExit(); + } + + int Main() + { + LStringPipe p; + LString err; + + auto result = LgiGetUri(this, &p, &err, url); + // LgiTrace("net(%s) = %i\n", url.Get(), result); + view->RunCallback([result, data=p.NewLStr(), this]() + { + callback(result, data); + }); + + return 0; + } + }; + + struct Image + { + int status = -1; + LString uri; + LAutoPtr img; + }; + + LArray threads; LiteHtmlView *view = NULL; LWindow *wnd = NULL; litehtml::document::ptr doc; LRect client; LString cursorName; + LString currentUrl; LHashTbl, LFont*> fontMap; + LHashTbl, Image*> imageCache; LiteHtmlViewPriv(LiteHtmlView *v) : view(v) { - } ~LiteHtmlViewPriv() { + Cancel(); + threads.DeleteObjects(); + // Do this before releasing other owned objects, like the fontMap. doc.reset(); + + // Clean up caches + imageCache.DeleteObjects(); + fontMap.DeleteObjects(); } LColour Convert(const litehtml::web_color &c) { return LColour(c.red, c.green, c.blue, c.alpha); } LRect Convert(const litehtml::position &p) { LRect r(p.x, p.y, p.x + p.width - 1, p.y + p.height - 1); return r; } void UpdateScreen(litehtml::position::vector &redraw) { if (redraw.size() > 0) { // FIXME: should invalidate just the dirty regions... view->Invalidate(); } } - litehtml::uint_ptr create_font( const char* faceName, int size, int weight, litehtml::font_style italic, unsigned int decoration, litehtml::font_metrics *fm) { litehtml::uint_ptr hnd; do { hnd = LRand(10000); } while (fontMap.Find(hnd) != NULL); LFont *fnt = new LFont; bool status = fnt->Create(faceName, LCss::Len(LCss::LenPt, size) ); if (!status) LgiTrace("%s:%i - failed to create font(%s,%i)\n", _FL, faceName, size); fontMap.Add(hnd, fnt); if (fm) { LDisplayString ds(fnt, "x"); fm->height = fnt->GetHeight(); fm->ascent = fnt->Ascent(); fm->descent = fnt->Descent(); fm->x_height = ds.Y(); fm->draw_spaces = false; } return hnd; } void delete_font(litehtml::uint_ptr hFont) { auto fnt = fontMap.Find(hFont); if (fnt) { delete fnt; fontMap.Delete(hFont); } } int text_width(const char* text, litehtml::uint_ptr hFont) { auto fnt = fontMap.Find(hFont); if (!fnt) return 0; LDisplayString ds(fnt, text); return ds.X(); } void draw_text(litehtml::uint_ptr hdc, const char* text, litehtml::uint_ptr hFont, litehtml::web_color color, const litehtml::position& pos) { auto pDC = (LSurface*)hdc; auto Fnt = fontMap.Find(hFont); if (!pDC || !Fnt) return; LDisplayString ds(Fnt, text); Fnt->Fore(Convert(color)); Fnt->Transparent(true); ds.Draw(pDC, pos.x, pos.y); } int pt_to_px(int pt) const { auto dpi = wnd->GetDpi(); int px = pt * dpi.x / 72; return px; } int get_default_font_size() const { return 13; // LSysFont->PointSize(); } const char *get_default_font_name() const { return LSysFont->Face(); } void draw_list_marker(litehtml::uint_ptr hdc, const litehtml::list_marker &marker) { auto pDC = (LSurface*)hdc; auto Fnt = fontMap.Find(marker.font); if (!pDC) return; pDC->Colour(Convert(marker.color)); switch (marker.marker_type) { case litehtml::list_style_type_disc: { pDC->FilledCircle(marker.pos.x, marker.pos.y, 3); break; } default: { - NOT_IMPL; + LgiTrace("%s:%i - draw_list_marker %i not impl\n", marker.marker_type); break; } } } void load_image(const char *src, const char *baseurl, bool redraw_on_ready) { - NOT_IMPL + if (!imageCache.Find(src)) + { + LgiTrace("load_image(%s,%s)\n", src, baseurl); + if (auto i = new Image) + { + i->uri = src; + imageCache.Add(src, i); + threads.Add(new NetworkThread(view, src, [i, this](auto status, auto data) + { + auto leaf = i->uri.SplitDelimit("/").Last(); + if (status) + { + LMemStream s(data.Get(), data.Length(), false); + i->status = i->img.Reset(GdcD->Load(&s, leaf)); + LgiTrace("load_image(%s) load status=%i\n", i->uri.Get(), i->status); + } + else + { + i->status = false; + LgiTrace("load_image(%s) network status=%i\n", i->uri.Get(), i->status); + } + + view->Invalidate(); + })); + } + } } void get_image_size(const char *src, const char *baseurl, litehtml::size &sz) { - NOT_IMPL + if (auto i = imageCache.Find(src)) + { + if (i->img) + { + sz.width = i->img->X(); + sz.height = i->img->Y(); + } + } } void draw_background(litehtml::uint_ptr hdc, const std::vector &background) { auto pDC = (LSurface*)hdc; for (auto b: background) { - pDC->Colour(Convert(b.color)); auto rc = Convert(b.border_box); - pDC->Rectangle(&rc); + if (!b.image.empty()) + { + if (auto i = imageCache.Find(b.image.c_str() )) + { + if (i->img) + { + pDC->Blt(b.position_x, b.position_y, i->img); + } + else LgiTrace("%s:%i - draw_background(img=%s) img no surface\n", _FL, b.image.c_str()); + } + else LgiTrace("%s:%i - draw_background(img=%s) img not found\n", _FL, b.image.c_str()); + } + else + { + pDC->Colour(Convert(b.color)); + pDC->Rectangle(&rc); + } } } void draw_borders(litehtml::uint_ptr hdc, const litehtml::borders& borders, const litehtml::position& draw_pos, bool root) { auto pDC = (LSurface*)hdc; auto drawEdge = [&](const litehtml::border &b, int x, int y, int dx, int dy, int ix, int iy) { pDC->Colour(Convert(b.color)); for (int i=0; iLine(x, y, x+dx, y+dy); x += ix; y += iy; } }; int x2 = draw_pos.width - 1; int y2 = draw_pos.height - 1; drawEdge(borders.left, draw_pos.x, draw_pos.y, 0, y2, 1, 0); drawEdge(borders.top, draw_pos.x, draw_pos.y, x2, 0, 0, 1); drawEdge(borders.right, draw_pos.x+x2, draw_pos.y, 0, y2, -1, 0); drawEdge(borders.bottom, draw_pos.x, draw_pos.y+y2, x2, 0, 0, -1); } void set_caption(const char* caption) { wnd->Name(caption); } void set_base_url(const char* base_url) { NOT_IMPL } void link(const std::shared_ptr& doc, const litehtml::element::ptr& el) { NOT_IMPL } void on_anchor_click(const char* url, const litehtml::element::ptr& el) { - NOT_IMPL + if (!url) + return; + + threads.Add(new NetworkThread(view, url, [this, url=LString(url)](auto status, auto data) + { + currentUrl = url; + doc = litehtml::document::createFromString(data, this); + view->OnNavigate(url); + view->Invalidate(); + })); } void set_cursor(const char* cursor) { cursorName = cursor; } void transform_text(litehtml::string& text, litehtml::text_transform tt) { NOT_IMPL } void import_css(litehtml::string& text, const litehtml::string& url, litehtml::string& baseurl) { - NOT_IMPL + LUri cur(currentUrl); + LUri u(url.c_str()); + LString newUri; + + if (u.sProtocol && u.sHost) + { + // Absolute URI + newUri = url.c_str(); + } + else if (char *s = u.sPath.Get()) + { + // Relative URI + if (*s == '/') + cur.sPath.Empty(); + cur.sPath = u.sPath; + newUri = cur.ToString(); + } + + if (newUri) + { + LStringPipe out; + LString err; + if (LgiGetUri(this, &out, &err, newUri)) + { + text = out.NewLStr().Get(); + } + else LgiTrace("%s:%i - error: LgiGetUri(%s)=%s (currentUrl=%s)\n", + _FL, newUri.Get(), err.Get(), currentUrl.Get()); + } + else LgiTrace("%s:%i - error: no uri for loading css.\n", _FL); } void set_clip(const litehtml::position& pos, const litehtml::border_radiuses& bdr_radius) { - NOT_IMPL + // NOT_IMPL } void del_clip() { - NOT_IMPL + // NOT_IMPL } void get_client_rect(litehtml::position &out) const { out = litehtml::position(client.x1, client.y1, client.X(), client.Y()); } litehtml::element::ptr create_element( const char* tag_name, const litehtml::string_map& attributes, const std::shared_ptr& doc) { return NULL; } void get_media_features(litehtml::media_features& media) const { // NOT_IMPL } void get_language(litehtml::string& language, litehtml::string& culture) const { NOT_IMPL } /* litehtml::string resolve_color(const litehtml::string &color) const { NOT_IMPL return litehtml::string(); } */ }; ///////////////////////////////////////////////////////////////// LiteHtmlView::LiteHtmlView(int id) { d = new LiteHtmlViewPriv(this); SetId(id); } LiteHtmlView::~LiteHtmlView() { delete d; } void LiteHtmlView::OnAttach() { d->wnd = GetWindow(); } LCursor LiteHtmlView::GetCursor(int x, int y) { if (d->cursorName == "pointer") return LCUR_PointingHand; return LCUR_Normal; } +void LiteHtmlView::OnNavigate(LString url) +{ + d->currentUrl = url; + Invalidate(); +} + bool LiteHtmlView::SetUrl(LString url) { if (LFileExists(url)) { auto html_text = LReadFile(url); if (html_text) { + d->currentUrl = url; d->doc = litehtml::document::createFromString(html_text, d); - Invalidate(); + OnNavigate(url); return d->doc != NULL; } } return false; } void LiteHtmlView::OnPaint(LSurface *pDC) { #ifdef WINDOWS LDoubleBuffer buf(pDC); #endif d->client = GetClient(); if (d->doc) { pDC->Colour(L_WORKSPACE); pDC->Rectangle(); auto width = pDC->X(); int r = d->doc->render(width); if (r) { auto width = d->doc->content_width(); auto height = d->doc->content_height(); if (height > Y()) { SetScrollBars(false, true); if (VScroll) { VScroll->SetRange(height); VScroll->SetPage(Y()); } } litehtml::position clip(0, 0, pDC->X(), pDC->Y()); d->doc->draw((litehtml::uint_ptr)pDC, 0, VScroll?-VScroll->Value():0, &clip); } } else { LLayout::OnPaint(pDC); } } int LiteHtmlView::OnNotify(LViewI *c, LNotification n) { - // printf("OnNotify %i=%i, %i=%i\n", c->GetId(), IDC_VSCROLL, n.Type, LNotifyValueChanged); + // LgiTrace("OnNotify %i=%i, %i=%i\n", c->GetId(), IDC_VSCROLL, n.Type, LNotifyValueChanged); if (c->GetId() == IDC_VSCROLL && n.Type == LNotifyValueChanged) { - // printf("Inval\n"); + // LgiTrace("Inval\n"); Invalidate(); } return LLayout::OnNotify(c, n); } void LiteHtmlView::OnMouseClick(LMouse &m) { if (!d->doc) return; int64_t sx, sy; GetScrollPos(sx, sy); litehtml::position::vector redraw_boxes; if (m.IsContextMenu()) { LSubMenu sub; sub.AppendItem("notImpl: submenu", -1, false); sub.Float(this, m); } else if (m.Left()) { if (m.Down()) d->doc->on_lbutton_down(m.x+sx, m.y+sy, m.x, m.y, redraw_boxes); else d->doc->on_lbutton_up(m.x+sx, m.y+sy, m.x, m.y, redraw_boxes); } d->UpdateScreen(redraw_boxes); } void LiteHtmlView::OnMouseMove(LMouse &m) { if (!d->doc) return; int64_t sx, sy; GetScrollPos(sx, sy); litehtml::position::vector redraw_boxes; d->doc->on_mouse_over(m.x+sx, m.y+sy, m.x, m.y, redraw_boxes); d->UpdateScreen(redraw_boxes); } diff --git a/test/LiteHtml/CMakeLists.txt b/test/LiteHtml/CMakeLists.txt --- a/test/LiteHtml/CMakeLists.txt +++ b/test/LiteHtml/CMakeLists.txt @@ -1,21 +1,23 @@ cmake_minimum_required(VERSION 3.18) project(LgiLiteHtml) set(LGI_LIB_ONLY TRUE) add_subdirectory(../.. lgi) add_subdirectory(../../../../../codelib/litehtml litehtml) set(SRC src/main.cpp ../../src/common/LiteHtml/LiteHtmlView.cpp + ../../src/common/Net/Http.cpp + ../../src/common/Net/OpenSSLSocket.cpp ../../src/common/Lgi/LgiMain.cpp) if(WIN32) set(APP_TYPE WIN32) else() set(APP_TYPE) endif() add_executable(LgiLiteHtml ${APP_TYPE} ${SRC}) target_link_libraries(LgiLiteHtml PUBLIC lgi litehtml) \ No newline at end of file diff --git a/test/LiteHtml/src/main.cpp b/test/LiteHtml/src/main.cpp --- a/test/LiteHtml/src/main.cpp +++ b/test/LiteHtml/src/main.cpp @@ -1,69 +1,84 @@ #include "lgi/common/Lgi.h" #include "lgi/common/Box.h" #include "lgi/common/Edit.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Layout.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/Menu.h" #include "lgi/common/LiteHtmlView.h" const char *AppName = "LgiLiteHtml"; enum Ctrls { IDC_BOX = 100, IDC_LOCATION, IDC_BROWSER, }; +class AppLiteHtmlView : public LiteHtmlView +{ +public: + AppLiteHtmlView(int id) : LiteHtmlView(id) + { + + } + + void OnNavigate(LString url) override + { + GetWindow()->SetCtrlName(IDC_LOCATION, url); + LiteHtmlView::OnNavigate(url); + } +}; + class App : public LWindow { LBox *box = NULL; LEdit *location = NULL; LiteHtmlView *browser = NULL; public: App() { LRect r(200, 200, 1400, 1000); SetPos(r); Name(AppName); MoveToCenter(); SetQuitOnClose(true); if (Attach(0)) { AddView(box = new LBox(IDC_BOX, true)); box->AddView(location = new LEdit(IDC_LOCATION, 0, 0, 100, 20)); - box->AddView(browser = new LiteHtmlView(IDC_BROWSER)); + box->AddView(browser = new AppLiteHtmlView(IDC_BROWSER)); box->Value(LSysFont->GetHeight() + 8); AttachChildren(); location->Focus(true); Visible(true); } } void SetUrl(LString s) { if (location) location->Name(s); if (browser) browser->SetUrl(s); } void OnReceiveFiles(LArray &Files) { SetUrl(Files[0]); } }; int LgiMain(OsAppArguments &AppArgs) { LApp app(AppArgs, "application/x-lgi-litehtml"); if (app.IsOk()) { app.AppWnd = new App; app.Run(); } return 0; }