diff --git a/Lvc/Src/VcCommit.cpp b/Lvc/Src/VcCommit.cpp --- a/Lvc/Src/VcCommit.cpp +++ b/Lvc/Src/VcCommit.cpp @@ -1,218 +1,247 @@ #include "Lvc.h" #include "GClipBoard.h" #include "../Resources/resdefs.h" VcCommit::VcCommit(AppPriv *priv) { d = priv; Current = false; + Parents.SetFixedLength(false); } char *VcCommit::GetRev() { return Rev; } char *VcCommit::GetAuthor() { return Author; } char *VcCommit::GetMsg() { return Msg; } void VcCommit::SetCurrent(bool b) { Current = b; } char *VcCommit::GetText(int Col) { switch (Col) { case 0: return Current ? (char*)"***" : NULL; case 1: return Rev; case 2: return Author; case 3: Cache = Ts.Get(); return Cache; case 4: if (!Msg) return NULL; Cache = Msg.Split("\n", 1)[0]; return Cache; } return NULL; } -bool VcCommit::GitParse(GString s) +bool VcCommit::GitParse(GString s, bool RevList) { GString::Array lines = s.Split("\n"); if (lines.Length() < 3) return false; - for (unsigned ln = 0; ln < lines.Length(); ln++) + if (RevList) { - GString &l = lines[ln]; - if (ln == 0) - Rev = l.SplitDelimit().Last(); - else if (l.Find("Author:") >= 0) - Author = l.Split(":", 1)[1].Strip(); - else if (l.Find("Date:") >= 0) - Ts.Parse(l.Split(":", 1)[1].Strip()); - else if (l.Strip().Length() > 0) + auto a = lines[0].SplitDelimit(); + if (a.Length() != 2) + return false; + Ts.Set(a[0].Int()); + Rev = a[1]; + + for (int i=0; i= 0) + Author = l.Split(":", 1)[1].Strip(); + else if (l.Find("Date:") >= 0) + Ts.Parse(l.Split(":", 1)[1].Strip()); + else if (l.Strip().Length() > 0) + { + if (Msg) + Msg += "\n"; + Msg += l.Strip(); + } } } return Author && Rev; } bool VcCommit::CvsParse(LDateTime &Dt, GString Auth, GString Message) { Ts = Dt; Ts.ToLocal(); uint64 i; if (Ts.Get(i)) Rev.Printf(LPrintfInt64, i); Author = Auth; Msg = Message; return true; } bool VcCommit::HgParse(GString s) { GString::Array Lines = s.SplitDelimit("\n"); if (Lines.Length() < 1) return false; for (GString *Ln = NULL; Lines.Iterate(Ln); ) { GString::Array f = Ln->Split(":", 1); if (f.Length() == 2) { if (f[0].Equals("changeset")) Rev = f[1].Strip(); else if (f[0].Equals("user")) Author = f[1].Strip(); else if (f[0].Equals("date")) Ts.Parse(f[1].Strip()); else if (f[0].Equals("summary")) Msg = f[1].Strip(); } } return Rev.Get() != NULL; } bool VcCommit::SvnParse(GString s) { GString::Array lines = s.Split("\n"); if (lines.Length() < 1) return false; for (unsigned ln = 0; ln < lines.Length(); ln++) { GString &l = lines[ln]; if (ln == 0) { GString::Array a = l.Split("|"); if (a.Length() > 3) { Rev = a[0].Strip(" \tr"); Author = a[1].Strip(); Ts.Parse(a[2]); } } else { if (Msg) Msg += "\n"; Msg += l.Strip(); } } Msg = Msg.Strip(); return Author && Rev && Ts.IsValid(); } VcFolder *VcCommit::GetFolder() { for (GTreeItem *i = d->Tree->Selection(); i; i = i->GetParent()) { auto f = dynamic_cast(i); if (f) return f; } return NULL; } void VcCommit::Select(bool b) { LListItem::Select(b); if (Rev && b) { VcFolder *f = GetFolder(); if (f) f->ListCommit(this); if (d->Msg) { d->Msg->Name(Msg); GWindow *w = d->Msg->GetWindow(); if (w) { w->SetCtrlEnabled(IDC_COMMIT, false); w->SetCtrlEnabled(IDC_COMMIT_AND_PUSH, false); } } else LgiAssert(0); } } void VcCommit::OnMouseClick(GMouse &m) { LListItem::OnMouseClick(m); if (m.IsContextMenu()) { GSubMenu s; s.AppendItem("Update", IDM_UPDATE, !Current); s.AppendItem("Copy Revision", IDM_COPY_REV); int Cmd = s.Float(GetList(), m); switch (Cmd) { case IDM_UPDATE: { VcFolder *f = GetFolder(); if (!f) { LgiAssert(!"No folder?"); break; } f->OnUpdate(Rev); break; } case IDM_COPY_REV: { GClipBoard c(GetList()); c.Text(Rev); break; } } } } diff --git a/Lvc/Src/VcCommit.h b/Lvc/Src/VcCommit.h --- a/Lvc/Src/VcCommit.h +++ b/Lvc/Src/VcCommit.h @@ -1,39 +1,40 @@ #ifndef _VcCommit_h_ #define _VcCommit_h_ class VcFolder; class VcCommit : public LListItem { AppPriv *d; bool Current; GString Rev; + GString::Array Parents; GString Author; LDateTime Ts; GString Cache; GString Msg; public: GString::Array Files; VcCommit(AppPriv *priv); char *GetRev(); char *GetAuthor(); char *GetMsg(); LDateTime &GetTs() { return Ts; } void SetCurrent(bool b); char *GetText(int Col); - bool GitParse(GString s); + bool GitParse(GString s, bool RevList); bool SvnParse(GString s); bool HgParse(GString s); bool CvsParse(LDateTime &Dt, GString Auth, GString Msg); VcFolder *GetFolder(); // Events void OnMouseClick(GMouse &m); void Select(bool b); }; #endif \ No newline at end of file diff --git a/Lvc/Src/VcFolder.cpp b/Lvc/Src/VcFolder.cpp --- a/Lvc/Src/VcFolder.cpp +++ b/Lvc/Src/VcFolder.cpp @@ -1,2400 +1,2467 @@ #include "Lvc.h" #include "../Resources/resdefs.h" #ifndef CALL_MEMBER_FN #define CALL_MEMBER_FN(object,ptrToMember) ((object).*(ptrToMember)) #endif int Ver2Int(GString v) { auto p = v.Split("."); int i = 0; for (auto s : p) { auto Int = s.Int(); if (Int < 256) { i <<= 8; - i |= (uint8)Int; + i |= (uint8_t)Int; } else { LgiAssert(0); return 0; } } return i; } int ToolVersion[VcMax] = {0}; ReaderThread::ReaderThread(GSubProcess *p, GStream *out) : LThread("ReaderThread") { Process = p; Out = out; Run(); } ReaderThread::~ReaderThread() { Out = NULL; while (!IsExited()) LgiSleep(1); } int ReaderThread::Main() { bool b = Process->Start(true, false); if (!b) { GString s("Process->Start failed.\n"); Out->Write(s.Get(), s.Length(), ErrSubProcessFailed); return ErrSubProcessFailed; } while (Process->IsRunning()) { if (Out) { char Buf[1024]; ssize_t r = Process->Read(Buf, sizeof(Buf)); if (r > 0) Out->Write(Buf, r); } else { Process->Kill(); return -1; break; } } if (Out) { char Buf[1024]; ssize_t r = Process->Read(Buf, sizeof(Buf)); if (r > 0) Out->Write(Buf, r); } return (int) Process->GetExitValue(); } ///////////////////////////////////////////////////////////////////////////////////////////// void VcFolder::Init(AppPriv *priv) { d = priv; IsCommit = false; IsLogging = false; IsGetCur = false; IsUpdate = false; IsFilesCmd = false; IsWorkingFld = false; CommitListDirty = false; IsUpdatingCounts = false; Unpushed = Unpulled = -1; Type = VcNone; CmdErrors = 0; Expanded(false); Insert(Tmp = new GTreeItem); Tmp->SetText("Loading..."); LgiAssert(d != NULL); } VcFolder::VcFolder(AppPriv *priv, const char *p) { Init(priv); Path = p; } VcFolder::VcFolder(AppPriv *priv, GXmlTag *t) { Init(priv); Serialize(t, false); } VersionCtrl VcFolder::GetType() { if (Type == VcNone) Type = DetectVcs(Path); return Type; } char *VcFolder::GetText(int Col) { switch (Col) { case 0: { if (Cmds.Length()) { Cache = Path; Cache += " (...)"; return Cache; } return Path; } case 1: { CountCache.Printf("%i/%i", Unpulled, Unpushed); CountCache = CountCache.Replace("-1", "--"); return CountCache; } } return NULL; } bool VcFolder::Serialize(GXmlTag *t, bool Write) { if (Write) t->SetContent(Path); else Path = t->GetContent(); return true; } GXmlTag *VcFolder::Save() { GXmlTag *t = new GXmlTag(OPT_Folder); if (t) Serialize(t, true); return t; } const char *VcFolder::GetVcName() { const char *Def = NULL; switch (GetType()) { case VcGit: Def = "git"; break; case VcSvn: Def = "svn"; break; case VcHg: Def = "hg"; break; case VcCvs: Def = "cvs"; break; default: break; } if (!VcCmd) { GString Opt; Opt.Printf("%s-path", Def); GVariant v; if (d->Opts.GetValue(Opt, v)) VcCmd = v.Str(); } if (!VcCmd) VcCmd = Def; return VcCmd; } bool VcFolder::StartCmd(const char *Args, ParseFn Parser, ParseParams *Params, LoggingType Logging) { const char *Exe = GetVcName(); if (!Exe) return false; if (CmdErrors > 2) return false; if (d->Log && Logging != LogSilo) d->Log->Print("%s %s\n", Exe, Args); GAutoPtr Process(new GSubProcess(Exe, Args)); if (!Process) return false; Process->SetInitFolder(Params && Params->AltInitPath ? Params->AltInitPath : Path); GString::Array Ctx; Ctx.SetFixedLength(false); Ctx.Add(Path); Ctx.Add(Exe); Ctx.Add(Args); GAutoPtr c(new Cmd(Ctx, Logging, d->Log)); if (!c) return false; c->PostOp = Parser; c->Params.Reset(Params); c->Rd.Reset(new ReaderThread(Process.Release(), c)); Cmds.Add(c.Release()); Update(); LgiTrace("Cmd: %s %s\n", Exe, Args); return true; } int LogDateCmp(LListItem *a, LListItem *b, NativeInt Data) { VcCommit *A = dynamic_cast(a); VcCommit *B = dynamic_cast(b); if ((A != NULL) ^ (B != NULL)) { // This handles keeping the "working folder" list item at the top return (A != NULL) - (B != NULL); } // Sort the by date from most recent to least return -A->GetTs().Compare(&B->GetTs()); } bool VcFolder::ParseBranches(int Result, GString s, ParseParams *Params) { switch (GetType()) { case VcGit: { GString::Array a = s.SplitDelimit("\r\n"); for (GString *l = NULL; a.Iterate(l); ) { GString n = l->Strip(); if (n(0) == '*') { GString::Array c = n.SplitDelimit(" \t", 1); if (c.Length() > 1) Branches.New() = CurrentBranch = c[1]; else Branches.New() = n; } else Branches.New() = n; } break; } case VcHg: { Branches = s.SplitDelimit("\r\n"); break; } default: { break; } } OnBranchesChange(); return false; } void VcFolder::OnBranchesChange() { GWindow *w = d->Tree->GetWindow(); if (!w) return; DropDownBtn *dd; if (w->GetViewById(IDC_BRANCH_DROPDOWN, dd)) { dd->SetList(IDC_BRANCH, Branches); } if (Branches.Length() > 0) { GViewI *b; if (w->GetViewById(IDC_BRANCH, b)) { if (!ValidStr(b->Name())) b->Name(Branches.First()); } } } void VcFolder::Select(bool b) { if (!b) { GWindow *w = d->Tree->GetWindow(); w->SetCtrlName(IDC_BRANCH, NULL); } GTreeItem::Select(b); if (b) { if (!DirExists(Path)) return; if ((Log.Length() == 0 || CommitListDirty) && !IsLogging) { - if (GetType() == VcSvn && CommitListDirty) - IsLogging = StartCmd("up", &VcFolder::ParsePull, new ParseParams("log")); - else - IsLogging = StartCmd("log", &VcFolder::ParseLog); + switch (GetType()) + { + case VcGit: + { + IsLogging = StartCmd("rev-list --all --header --timestamp", &VcFolder::ParseRevList); + break; + } + case VcSvn: + { + if (CommitListDirty) + { + IsLogging = StartCmd("up", &VcFolder::ParsePull, new ParseParams("log")); + break; + } + // else fall through + } + default: + { + IsLogging = StartCmd("log", &VcFolder::ParseLog); + } + } CommitListDirty = false; } if (Branches.Length() == 0) { switch (GetType()) { case VcGit: StartCmd("branch -a", &VcFolder::ParseBranches); break; case VcSvn: Branches.New() = "trunk"; break; case VcHg: StartCmd("branch", &VcFolder::ParseBranches); break; case VcCvs: break; default: LgiAssert(!"Impl me."); break; } } OnBranchesChange(); /* if (!IsUpdatingCounts && Unpushed < 0) { switch (GetType()) { case VcGit: IsUpdatingCounts = StartCmd("cherry -v", &VcFolder::ParseCounts); break; case VcSvn: IsUpdatingCounts = StartCmd("status -u", &VcFolder::ParseCounts); break; default: LgiAssert(!"Impl me."); break; } } */ char *Ctrl = d->Lst->GetWindow()->GetCtrlName(IDC_FILTER); GString Filter = ValidStr(Ctrl) ? Ctrl : NULL; if (d->CurFolder != this) { d->CurFolder = this; d->Lst->RemoveAll(); } if (!Uncommit) Uncommit.Reset(new UncommitedItem(d)); d->Lst->Insert(Uncommit, 0); int64 CurRev = Atoi(CurrentCommit.Get()); List Ls; for (unsigned i=0; iGetRev()) { switch (GetType()) { case VcSvn: { int64 LogRev = Atoi(Log[i]->GetRev()); if (CurRev >= 0 && CurRev >= LogRev) { CurRev = -1; Log[i]->SetCurrent(true); } else { Log[i]->SetCurrent(false); } break; } default: Log[i]->SetCurrent(!_stricmp(CurrentCommit, Log[i]->GetRev())); break; } } bool Add = !Filter; if (Filter) { const char *s = Log[i]->GetRev(); if (s && strstr(s, Filter) != NULL) Add = true; s = Log[i]->GetAuthor(); if (s && stristr(s, Filter) != NULL) Add = true; s = Log[i]->GetMsg(); if (s && stristr(s, Filter) != NULL) Add = true; } LList *CurOwner = Log[i]->GetList(); if (Add ^ (CurOwner != NULL)) { if (Add) Ls.Insert(Log[i]); else d->Lst->Remove(Log[i]); } } d->Lst->Insert(Ls); d->Lst->Sort(LogDateCmp); if (GetType() == VcGit) { d->Lst->ColumnAt(0)->Width(40); d->Lst->ColumnAt(1)->Width(270); d->Lst->ColumnAt(2)->Width(240); d->Lst->ColumnAt(3)->Width(130); d->Lst->ColumnAt(4)->Width(400); } else d->Lst->ResizeColumnsToContent(); d->Lst->UpdateAllItems(); if (!CurrentCommit && !IsGetCur) { switch (GetType()) { case VcGit: IsGetCur = StartCmd("rev-parse HEAD", &VcFolder::ParseInfo); break; case VcSvn: IsGetCur = StartCmd("info", &VcFolder::ParseInfo); break; case VcHg: IsGetCur = StartCmd("id -i", &VcFolder::ParseInfo); break; case VcCvs: break; default: LgiAssert(!"Impl me."); break; } } } } int CommitRevCmp(VcCommit **a, VcCommit **b) { int64 arev = Atoi((*a)->GetRev()); int64 brev = Atoi((*b)->GetRev()); int64 diff = (int64)brev - arev; if (diff < 0) return -1; return (diff > 0) ? 1 : 0; } int CommitDateCmp(VcCommit **a, VcCommit **b) { uint64 ats, bts; (*a)->GetTs().Get(ats); (*b)->GetTs().Get(bts); int64 diff = (int64)bts - ats; if (diff < 0) return -1; return (diff > 0) ? 1 : 0; } +bool VcFolder::ParseRevList(int Result, GString s, ParseParams *Params) +{ + LHashTbl, VcCommit*> Map; + for (VcCommit **pc = NULL; Log.Iterate(pc); ) + Map.Add((*pc)->GetRev(), *pc); + + int Skipped = 0, Errors = 0; + switch (GetType()) + { + case VcGit: + { + GString::Array Commits; + Commits.SetFixedLength(false); + + // Split on the NULL chars... + char *c = s.Get(); + char *e = c + s.Length(); + while (c < e) + { + char *nul = c; + while (nul < e && *nul) nul++; + if (nul <= c) break; + Commits.New().Set(c, nul-c); + if (nul >= e) break; + c = nul + 1; + } + + for (auto Commit: Commits) + { + GAutoPtr Rev(new VcCommit(d)); + if (Rev->GitParse(Commit, true)) + { + if (!Map.Find(Rev->GetRev())) + Log.Add(Rev.Release()); + else + Skipped++; + } + else + { + LgiTrace("%s:%i - Failed:\n%s\n\n", _FL, Commit.Get()); + Errors++; + } + } + break; + } + } + + return true; +} + bool VcFolder::ParseLog(int Result, GString s, ParseParams *Params) { LHashTbl, VcCommit*> Map; for (VcCommit **pc = NULL; Log.Iterate(pc); ) Map.Add((*pc)->GetRev(), *pc); int Skipped = 0, Errors = 0; switch (GetType()) { case VcGit: { GString::Array c; c.SetFixedLength(false); char *prev = s.Get(); for (char *i = s.Get(); *i; ) { if (!strnicmp(i, "commit ", 7)) { if (i > prev) { c.New().Set(prev, i - prev); // LgiTrace("commit=%i\n", (int)(i - prev)); } prev = i; } while (*i) { if (*i++ == '\n') break; } } for (unsigned i=0; i Rev(new VcCommit(d)); - if (Rev->GitParse(c[i])) + if (Rev->GitParse(c[i], false)) { if (!Map.Find(Rev->GetRev())) Log.Add(Rev.Release()); else Skipped++; } else { LgiTrace("%s:%i - Failed:\n%s\n\n", _FL, c[i].Get()); Errors++; } } Log.Sort(CommitDateCmp); break; } case VcSvn: { GString::Array c = s.Split("------------------------------------------------------------------------"); for (unsigned i=0; i Rev(new VcCommit(d)); GString Raw = c[i].Strip(); if (Rev->SvnParse(Raw)) { if (!Map.Find(Rev->GetRev())) Log.Add(Rev.Release()); else Skipped++; } else if (Raw) { LgiTrace("%s:%i - Failed:\n%s\n\n", _FL, Raw.Get()); Errors++; } } Log.Sort(CommitRevCmp); break; } case VcHg: { GString::Array c = s.Split("\n\n"); for (GString *Commit = NULL; c.Iterate(Commit); ) { GAutoPtr Rev(new VcCommit(d)); if (Rev->HgParse(*Commit)) { if (!Map.Find(Rev->GetRev())) Log.Add(Rev.Release()); } } break; } case VcCvs: { LHashTbl, VcCommit*> Map; GString::Array c = s.Split("============================================================================="); for (GString *Commit = NULL; c.Iterate(Commit);) { if (Commit->Strip().Length()) { GString Head, File; GString::Array Versions = Commit->Split("----------------------------"); GString::Array Lines = Versions[0].SplitDelimit("\r\n"); for (GString *Line = NULL; Lines.Iterate(Line);) { GString::Array p = Line->Split(":", 1); if (p.Length() == 2) { // LgiTrace("Line: %s\n", Line->Get()); GString Var = p[0].Strip().Lower(); GString Val = p[1].Strip(); if (Var.Equals("branch")) { if (Val.Length()) Branches.Add(Val); } else if (Var.Equals("head")) { Head = Val; } else if (Var.Equals("rcs file")) { GString::Array f = Val.SplitDelimit(","); File = f.First(); } } } // LgiTrace("%s\n", Commit->Get()); for (unsigned i=1; i= 3) { GString Ver = Lines[0].Split(" ").Last(); GString::Array a = Lines[1].SplitDelimit(";"); GString Date = a[0].Split(":", 1).Last().Strip(); GString Author = a[1].Split(":", 1).Last().Strip(); GString Id = a[2].Split(":", 1).Last().Strip(); GString Msg = Lines[2]; LDateTime Dt; if (Dt.Parse(Date)) { uint64 Ts; if (Dt.Get(Ts)) { VcCommit *Cc = Map.Find(Ts); if (!Cc) { Map.Add(Ts, Cc = new VcCommit(d)); Log.Add(Cc); Cc->CvsParse(Dt, Author, Msg); } Cc->Files.Add(File.Get()); } else LgiAssert(!"NO ts for date."); } else LgiAssert(!"Date parsing failed."); } } } } break; } default: LgiAssert(!"Impl me."); break; } // LgiTrace("%s:%i - ParseLog: Skip=%i, Error=%i\n", _FL, Skipped, Errors); IsLogging = false; return true; } VcFile *VcFolder::FindFile(const char *Path) { if (!Path) return NULL; GArray Files; if (d->Files->GetAll(Files)) { GString p = Path; p = p.Replace(DIR_STR, "/"); for (auto f : Files) { auto Fn = f->GetFileName(); if (p.Equals(Fn)) { return f; } } } return NULL; } void VcFolder::OnCmdError(GString Output, const char *Msg) { if (!CmdErrors) { d->Log->Write(Output, Output.Length()); GString::Array a = GetProgramsInPath(GetVcName()); d->Log->Print("'%s' executables in the path:\n", GetVcName()); for (auto Bin : a) d->Log->Print(" %s\n", Bin.Get()); } CmdErrors++; d->Tabs->Value(1); Color(GColour::Red); } void VcFolder::ClearError() { Color(GCss::ColorInherit); } bool VcFolder::ParseInfo(int Result, GString s, ParseParams *Params) { switch (GetType()) { case VcGit: case VcHg: { CurrentCommit = s.Strip(); break; } case VcSvn: { if (s.Find("client is too old") >= 0) { OnCmdError(s, "Client too old"); break; } GString::Array c = s.Split("\n"); for (unsigned i=0; iIsWorking = true; ParseStatus(Result, s, Params); } else ParseDiffs(s, NULL, true); IsWorkingFld = false; d->Files->ResizeColumnsToContent(); if (GetType() == VcSvn) { Unpushed = d->Files->Length() > 0 ? 1 : 0; Update(); } return false; } bool VcFolder::ParseDiff(int Result, GString s, ParseParams *Params) { ParseDiffs(s, NULL, true); return false; } void VcFolder::Diff(VcFile *file) { switch (GetType()) { case VcSvn: case VcGit: case VcHg: { GString a; a.Printf("diff \"%s\"", file->GetFileName()); StartCmd(a, &VcFolder::ParseDiff); break; } default: LgiAssert(!"Impl me."); break; } } bool VcFolder::ParseDiffs(GString s, GString Rev, bool IsWorking) { LgiAssert(IsWorking || Rev.Get() != NULL); switch (GetType()) { case VcGit: { GString::Array a = s.Split("\n"); GString Diff; VcFile *f = NULL; for (unsigned i=0; iSetDiff(Diff); Diff.Empty(); auto Bits = a[i].SplitDelimit(); GString Fn, State = "M"; if (Bits[1].Equals("--cc")) { Fn = Bits.Last(); State = "C"; } else Fn = Bits.Last()(2,-1); LgiTrace("%s\n", a[i].Get()); f = FindFile(Fn); if (!f) f = new VcFile(d, this, Rev, IsWorking); f->SetText(State, COL_STATE); f->SetText(Fn.Replace("\\","/"), COL_FILENAME); f->GetStatus(); d->Files->Insert(f); } else if (!_strnicmp(Ln, "new file", 8)) { if (f) f->SetText("A", COL_STATE); } else if (!_strnicmp(Ln, "index", 5) || !_strnicmp(Ln, "commit", 6) || !_strnicmp(Ln, "Author:", 7) || !_strnicmp(Ln, "Date:", 5) || !_strnicmp(Ln, "+++", 3) || !_strnicmp(Ln, "---", 3)) { // Ignore } else { if (Diff) Diff += "\n"; Diff += a[i]; } } if (f && Diff) { f->SetDiff(Diff); Diff.Empty(); } break; } case VcHg: { GString::Array a = s.Split("\n"); GString Diff; VcFile *f = NULL; for (unsigned i=0; iSetDiff(Diff); Diff.Empty(); GString Fn = a[i].Split(" ").Last(); f = new VcFile(d, this, Rev, IsWorking); f->SetText(Fn.Replace("\\","/"), COL_FILENAME); d->Files->Insert(f); } else if (!_strnicmp(Ln, "index", 5) || !_strnicmp(Ln, "commit", 6) || !_strnicmp(Ln, "Author:", 7) || !_strnicmp(Ln, "Date:", 5) || !_strnicmp(Ln, "+++", 3) || !_strnicmp(Ln, "---", 3)) { // Ignore } else { if (Diff) Diff += "\n"; Diff += a[i]; } } if (f && Diff) { f->SetDiff(Diff); Diff.Empty(); } break; } case VcSvn: { GString::Array a = s.Replace("\r").Split("\n"); GString Diff; VcFile *f = NULL; bool InPreamble = false; bool InDiff = false; for (unsigned i=0; iSetDiff(Diff); f->Select(false); } Diff.Empty(); InDiff = false; InPreamble = false; GString Fn = a[i].Split(":", 1).Last().Strip(); f = FindFile(Fn); if (!f) f = new VcFile(d, this, Rev, IsWorking); f->SetText(Fn.Replace("\\","/"), COL_FILENAME); f->SetText("M", COL_STATE); f->GetStatus(); d->Files->Insert(f); } else if (!_strnicmp(Ln, "------", 6)) { InPreamble = !InPreamble; } else if (!_strnicmp(Ln, "======", 6)) { InPreamble = false; InDiff = true; } else if (InDiff) { if (!strncmp(Ln, "--- ", 4) || !strncmp(Ln, "+++ ", 4)) { } else { if (Diff) Diff += "\n"; Diff += a[i]; } } } if (f && Diff) { f->SetDiff(Diff); Diff.Empty(); } break; } case VcCvs: { break; } default: { LgiAssert(!"Impl me."); break; } } return true; } bool VcFolder::ParseFiles(int Result, GString s, ParseParams *Params) { d->ClearFiles(); ParseDiffs(s, Params->Str, false); IsFilesCmd = false; d->Files->ResizeColumnsToContent(); return false; } void VcFolder::OnPulse() { bool Reselect = false, CmdsChanged = false; static bool Processing = false; if (!Processing) { Processing = true; // Lock out processing, if it puts up a dialog or something... // bad things happen if we try and re-process something. for (unsigned i=0; iRd->IsExited()) { GString s = c->GetBuf(); int Result = c->Rd->ExitCode(); if (Result == ErrSubProcessFailed) { if (!CmdErrors) d->Log->Print("Error: Can't run '%s'\n", GetVcName()); CmdErrors++; } if (c->PostOp) Reselect |= CALL_MEMBER_FN(*this, c->PostOp)(Result, s, c->Params); Cmds.DeleteAt(i--, true); delete c; CmdsChanged = true; } } Processing = false; } if (Reselect) { if (GTreeItem::Select()) Select(true); } if (CmdsChanged) Update(); } void VcFolder::OnRemove() { GXmlTag *t = d->Opts.LockTag(NULL, _FL); if (t) { for (GXmlTag *c = t->Children.First(); c; c = t->Children.Next()) { if (c->IsTag(OPT_Folder) && c->GetContent() && !_stricmp(c->GetContent(), Path)) { c->RemoveTag(); delete c; break; } } d->Opts.Unlock(); } } void VcFolder::OnMouseClick(GMouse &m) { if (m.IsContextMenu()) { GSubMenu s; s.AppendItem("Browse To", IDM_BROWSE_FOLDER); s.AppendItem( #ifdef WINDOWS "Command Prompt At", #else "Terminal At", #endif IDM_TERMINAL); s.AppendItem("Clean", IDM_CLEAN); s.AppendSeparator(); s.AppendItem("Remove", IDM_REMOVE); int Cmd = s.Float(GetTree(), m); switch (Cmd) { case IDM_BROWSE_FOLDER: { LgiBrowseToFile(Path); break; } case IDM_TERMINAL: { #if defined(MAC) LgiExecute("/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal", Path); #elif defined(WINDOWS) TCHAR w[MAX_PATH]; auto r = GetWindowsDirectory(w, CountOf(w)); if (r > 0) { GFile::Path p = GString(w); p += "system32\\cmd.exe"; FileDev->SetCurrentFolder(Path); LgiExecute(p); } #elif defined(LINUX) // #error "Impl me." #endif break; } case IDM_CLEAN: { Clean(); break; } case IDM_REMOVE: { OnRemove(); delete this; break; } default: break; } } } void VcFolder::OnUpdate(const char *Rev) { if (!Rev) return; if (!IsUpdate) { GString Args; NewRev = Rev; switch (GetType()) { case VcGit: Args.Printf("checkout %s", Rev); IsUpdate = StartCmd(Args, &VcFolder::ParseUpdate, NULL, LogNormal); break; case VcSvn: Args.Printf("up -r %s", Rev); IsUpdate = StartCmd(Args, &VcFolder::ParseUpdate, NULL, LogNormal); break; default: { LgiAssert(!"Impl me."); break; } } } } /////////////////////////////////////////////////////////////////////////////////////// int FolderCompare(GTreeItem *a, GTreeItem *b, NativeInt UserData) { VcLeaf *A = dynamic_cast(a); VcLeaf *B = dynamic_cast(b); if (!A || !B) return 0; return A->Compare(B); } void VcFolder::ReadDir(GTreeItem *Parent, const char *Path) { // Read child items GDirectory Dir; for (int b = Dir.First(Path); b; b = Dir.Next()) { if (Dir.IsDir()) { if (Dir.GetName()[0] != '.') { new VcLeaf(this, Parent, Path, Dir.GetName(), true); } } else if (!Dir.IsHidden()) { char *Ext = LgiGetExtension(Dir.GetName()); if (!Ext) continue; if (!stricmp(Ext, "c") || !stricmp(Ext, "cpp") || !stricmp(Ext, "h")) { new VcLeaf(this, Parent, Path, Dir.GetName(), false); } } } Parent->SortChildren(FolderCompare); } void VcFolder::OnExpand(bool b) { if (Tmp && b) { Tmp->Remove(); DeleteObj(Tmp); ReadDir(this, Path); } } void VcFolder::OnPaint(ItemPaintCtx &Ctx) { auto c = Color(); if (c.IsValid()) Ctx.Fore = c; c = BackgroundColor(); if (c.IsValid() && !GTreeItem::Select()) Ctx.Back = c; GTreeItem::OnPaint(Ctx); } void VcFolder::ListCommit(VcCommit *c) { if (!IsFilesCmd) { GString Args; switch (GetType()) { case VcGit: // Args.Printf("show --oneline --name-only %s", Rev); Args.Printf("show %s", c->GetRev()); IsFilesCmd = StartCmd(Args, &VcFolder::ParseFiles, new ParseParams(c->GetRev())); break; case VcSvn: Args.Printf("log --verbose --diff -r %s", c->GetRev()); IsFilesCmd = StartCmd(Args, &VcFolder::ParseFiles, new ParseParams(c->GetRev())); break; case VcCvs: { d->ClearFiles(); for (unsigned i=0; iFiles.Length(); i++) { VcFile *f = new VcFile(d, this, c->GetRev(), false); if (f) { f->SetText(c->Files[i], COL_FILENAME); d->Files->Insert(f); } } d->Files->ResizeColumnsToContent(); break; } default: LgiAssert(!"Impl me."); break; } if (IsFilesCmd) d->ClearFiles(); } } GString ConvertUPlus(GString s) { - GArray c; + GArray c; GUtf8Ptr p(s); int32 ch; while ((ch = p)) { if (ch == '{') { auto n = p.GetPtr(); if (n[1] == 'U' && n[2] == '+') { // Convert unicode code point p += 3; ch = (int32)htoi(p.GetPtr()); c.Add(ch); while ((ch = p) != '}') p++; } else c.Add(ch); } else c.Add(ch); p++; } c.Add(0); #ifdef LINUX return GString((char16*)c.AddressOf()); #else return GString(c.AddressOf()); #endif } bool VcFolder::ParseStatus(int Result, GString s, ParseParams *Params) { bool ShowUntracked = d->Files->GetWindow()->GetCtrlValue(IDC_UNTRACKED) != 0; bool IsWorking = Params ? Params->IsWorking : false; List Ins; switch (GetType()) { case VcCvs: { GString::Array a = s.Split("==================================================================="); for (auto i : a) { GString::Array Lines = i.SplitDelimit("\r\n"); GString f = Lines[0].Strip(); if (f.Find("File:") == 0) { GString::Array Parts = f.SplitDelimit("\t"); GString File = Parts[0].Split(": ").Last(); GString Status = Parts[1].Split(": ").Last(); GString WorkingRev; for (auto l : Lines) { GString::Array p = l.Strip().Split(":", 1); if (p.Length() > 1 && p[0].Strip().Equals("Working revision")) { WorkingRev = p[1].Strip(); } } VcFile *f = new VcFile(d, this, WorkingRev, IsWorking); f->SetText(Status, COL_STATE); f->SetText(File, COL_FILENAME); Ins.Insert(f); } else if (f(0) == '?' && ShowUntracked) { GString File = f(2, -1); VcFile *f = new VcFile(d, this, NULL, IsWorking); f->SetText("?", COL_STATE); f->SetText(File, COL_FILENAME); Ins.Insert(f); } } break; } case VcGit: { GString::Array Lines = s.SplitDelimit("\r\n"); int Fmt = ToolVersion[VcGit] >= Ver2Int("2.8.0") ? 2 : 1; for (auto Ln : Lines) { char Type = Ln(0); if (Ln.Lower().Find("error:") >= 0) { } else if (Ln.Find("usage: git") >= 0) { // It's probably complaining about the --porcelain=2 parameter OnCmdError(s, "Args error"); } else if (Type != '?') { VcFile *f = NULL; if (Fmt == 2) { GString::Array p = Ln.SplitDelimit(" ", 8); if (p.Length() < 7) d->Log->Print("%s:%i - Error: not enough tokens: '%s'\n", _FL, Ln.Get()); else { f = new VcFile(d, this, p[6], IsWorking); f->SetText(p[1].Strip("."), COL_STATE); f->SetText(p.Last(), COL_FILENAME); } } else if (Fmt == 1) { GString::Array p = Ln.SplitDelimit(" "); f = new VcFile(d, this, NULL, IsWorking); f->SetText(p[0], COL_STATE); f->SetText(p.Last(), COL_FILENAME); } if (f) Ins.Insert(f); } else if (ShowUntracked) { VcFile *f = new VcFile(d, this, NULL, IsWorking); f->SetText("?", COL_STATE); f->SetText(Ln(2,-1), COL_FILENAME); Ins.Insert(f); } } break; } case VcHg: case VcSvn: { GString::Array Lines = s.SplitDelimit("\r\n"); for (auto Ln : Lines) { char Type = Ln(0); if (Ln.Lower().Find("error:") >= 0) { } else if (Ln.Find("client is too old") >= 0) { OnCmdError(s, "Client too old."); return false; } else if (Strchr(" \t", Type) || Ln.Find("Summary of conflicts") >= 0) { // Ignore } else if (Type != '?') { GString::Array p = Ln.SplitDelimit(" ", 1); if (p.Length() == 2) { GString File; if (GetType() == VcSvn) File = ConvertUPlus(p.Last()); else File = p.Last(); VcFile *f = new VcFile(d, this, NULL, IsWorking); f->SetText(p[0], COL_STATE); f->SetText(File.Replace("\\","/"), COL_FILENAME); f->GetStatus(); Ins.Insert(f); } else LgiAssert(!"What happen?"); } else if (ShowUntracked) { VcFile *f = new VcFile(d, this, NULL, IsWorking); f->SetText("?", COL_STATE); f->SetText(Ln(2,-1), COL_FILENAME); Ins.Insert(f); } } break; } default: { LgiAssert(!"Impl me."); break; } } Unpushed = Ins.Length() > 0; Update(); if (GTreeItem::Select()) { d->Files->Insert(Ins); d->Files->ResizeColumnsToContent(); } else { Ins.DeleteObjects(); } return false; // Don't refresh list } void VcFolder::FolderStatus(const char *Path, VcLeaf *Notify) { if (GTreeItem::Select()) d->ClearFiles(); GString Arg; switch (GetType()) { case VcSvn: case VcHg: Arg = "status"; break; case VcCvs: Arg = "status -l"; break; case VcGit: if (!ToolVersion[VcGit]) LgiAssert(!"Where is the version?"); // What version did =2 become available? It's definately not in v2.5.4 // Not in v2.7.4 either... if (ToolVersion[VcGit] >= Ver2Int("2.8.0")) Arg = "status --porcelain=2"; else Arg = "status --porcelain"; break; default: return; } ParseParams *p = new ParseParams; if (Path && Notify) { p->AltInitPath = Path; p->Leaf = Notify; } else { p->IsWorking = true; } StartCmd(Arg, &VcFolder::ParseStatus, p); } void VcFolder::ListWorkingFolder() { if (!IsWorkingFld) { d->ClearFiles(); GString Arg; switch (GetType()) { case VcCvs: Arg = "-q diff --brief"; break; case VcSvn: Arg = "status"; break; case VcGit: StartCmd("diff --staged", &VcFolder::ParseWorking); Arg = "diff --diff-filter=ACDMRTU"; // return FolderStatus(); break; default: Arg ="diff"; break; } IsWorkingFld = StartCmd(Arg, &VcFolder::ParseWorking); } } void VcFolder::GitAdd() { if (!PostAdd) return; GString Args; if (PostAdd->Files.Length() == 0) { GString m(PostAdd->Msg); m = m.Replace("\"", "\\\""); Args.Printf("commit -m \"%s\"", m.Get()); IsCommit = StartCmd(Args, &VcFolder::ParseCommit, PostAdd->Param, LogNormal); PostAdd.Reset(); } else { GString Last = PostAdd->Files.Last(); Args.Printf("add \"%s\"", Last.Replace("\"", "\\\"").Replace("/", DIR_STR).Get()); PostAdd->Files.PopLast(); StartCmd(Args, &VcFolder::ParseGitAdd, NULL, LogNormal); } } bool VcFolder::ParseGitAdd(int Result, GString s, ParseParams *Params) { GitAdd(); return false; } bool VcFolder::ParseCommit(int Result, GString s, ParseParams *Params) { if (GTreeItem::Select()) Select(true); CommitListDirty = Result == 0; CurrentCommit.Empty(); IsCommit = false; if (Result) { switch (GetType()) { case VcGit: { if (s.Find("Please tell me who you are") >= 0) { { GInput i(GetTree(), "", "Git user name:", AppName); if (i.DoModal()) { GString Args; Args.Printf("config --global user.name \"%s\"", i.GetStr().Get()); StartCmd(Args); } } { GInput i(GetTree(), "", "Git user email:", AppName); if (i.DoModal()) { GString Args; Args.Printf("config --global user.email \"%s\"", i.GetStr().Get()); StartCmd(Args); } } } break; } default: break; } return false; } if (Result == 0 && GTreeItem::Select()) { d->ClearFiles(); GWindow *w = d->Diff ? d->Diff->GetWindow() : NULL; if (w) w->SetCtrlName(IDC_MSG, NULL); } switch (GetType()) { case VcGit: { Unpushed++; Update(); if (Params && Params->Str.Find("Push") >= 0) Push(); break; } case VcSvn: { CurrentCommit.Empty(); CommitListDirty = true; GetTree()->SendNotify(LvcCommandEnd); if (!Result) { Unpushed = 0; Update(); } break; } default: { LgiAssert(!"Impl me."); break; } } return true; } void VcFolder::Commit(const char *Msg, const char *Branch, bool AndPush) { VcFile *f = NULL; GArray Add; bool Partial = false; while (d->Files->Iterate(f)) { int c = f->Checked(); if (c > 0) Add.Add(f); else Partial = true; } if (CurrentBranch && Branch && !CurrentBranch.Equals(Branch)) { int Response = LgiMsg(GetTree(), "Do you want to start a new branch?", AppName, MB_YESNO); if (Response != IDYES) return; } if (!IsCommit) { GString Args; ParseParams *Param = AndPush ? new ParseParams("Push") : NULL; switch (GetType()) { case VcGit: { if (Add.Length() == 0) { break; } else if (Partial) { if (PostAdd.Reset(new GitCommit)) { PostAdd->Files.SetFixedLength(false); for (auto f : Add) PostAdd->Files.Add(f->GetFileName()); PostAdd->Msg = Msg; PostAdd->Branch = Branch; PostAdd->Param = Param; GitAdd(); } } else { GString m(Msg); m = m.Replace("\"", "\\\""); Args.Printf("commit -am \"%s\"", m.Get()); IsCommit = StartCmd(Args, &VcFolder::ParseCommit, Param, LogNormal); } break; } case VcSvn: { GString::Array a; a.New().Printf("commit -m \"%s\"", Msg); for (VcFile **pf = NULL; Add.Iterate(pf); ) { GString s = (*pf)->GetFileName(); if (s.Find(" ") >= 0) a.New().Printf("\"%s\"", s.Get()); else a.New() = s; } Args = GString(" ").Join(a); IsCommit = StartCmd(Args, &VcFolder::ParseCommit, NULL, LogNormal); if (d->Tabs && IsCommit) { d->Tabs->Value(1); GetTree()->SendNotify(LvcCommandStart); } break; } default: { LgiAssert(!"Impl me."); break; } } } } void VcFolder::Push() { GString Args; bool Working = false; switch (GetType()) { case VcGit: Working = StartCmd("push", &VcFolder::ParsePush, NULL, LogNormal); break; case VcSvn: // Nothing to do here.. the commit pushed the data already break; default: LgiAssert(!"Impl me."); break; } if (d->Tabs && Working) { d->Tabs->Value(1); GetTree()->SendNotify(LvcCommandStart); } } bool VcFolder::ParsePush(int Result, GString s, ParseParams *Params) { bool Status = false; if (Result) { OnCmdError(s, "Push failed."); } else { ClearError(); switch (GetType()) { case VcGit: break; case VcSvn: break; default: break; } Unpushed = 0; Update(); Status = true; } GetTree()->SendNotify(LvcCommandEnd); return Status; // no reselect } void VcFolder::Pull(LoggingType Logging) { GString Args; bool Status = false; switch (GetType()) { case VcHg: case VcGit: Status = StartCmd("pull", &VcFolder::ParsePull, NULL, Logging); break; case VcSvn: Status = StartCmd("up", &VcFolder::ParsePull, NULL, Logging); break; default: LgiAssert(!"Impl me."); break; } if (d->Tabs && Status) { d->Tabs->Value(1); GetTree()->SendNotify(LvcCommandStart); } } bool VcFolder::ParsePull(int Result, GString s, ParseParams *Params) { if (Result) { OnCmdError(s, "Pull failed."); return false; } else ClearError(); switch (GetType()) { case VcGit: case VcHg: { // Git does a merge by default, so the current commit changes... CurrentCommit.Empty(); break; } case VcSvn: { // Svn also does a merge by default and can update our current position... CurrentCommit.Empty(); GString::Array a = s.SplitDelimit("\r\n"); for (GString *Ln = NULL; a.Iterate(Ln); ) { if (Ln->Find("At revision") >= 0) { GString::Array p = Ln->SplitDelimit(" ."); CurrentCommit = p.Last(); break; } else if (Ln->Find("svn cleanup") >= 0) { OnCmdError(s, "Needs cleanup"); break; } } if (Params && Params->Str.Equals("log")) { IsLogging = StartCmd("log", &VcFolder::ParseLog); return false; } break; } default: break; } GetTree()->SendNotify(LvcCommandEnd); CommitListDirty = true; return true; // Yes - reselect and update } void VcFolder::Clean() { switch (GetType()) { case VcSvn: StartCmd("cleanup", &VcFolder::ParseClean, NULL, LogNormal); break; default: LgiMsg(GetTree(), "Not implemented.", AppName); break; } } bool VcFolder::ParseClean(int Result, GString s, ParseParams *Params) { switch (GetType()) { case VcSvn: if (Result == 0) Color(ColorInherit); break; default: LgiAssert(!"Impl me."); break; } return false; } void VcFolder::GetVersion() { auto t = GetType(); switch (t) { case VcGit: case VcSvn: case VcHg: case VcCvs: StartCmd("--version", &VcFolder::ParseVersion, NULL, LogNormal); break; default: OnCmdError(NULL, "No version control found."); break; } } bool VcFolder::ParseVersion(int Result, GString s, ParseParams *Params) { auto p = s.SplitDelimit(); switch (GetType()) { case VcGit: { if (p.Length() > 2) { ToolVersion[GetType()] = Ver2Int(p[2]); printf("Git version: %s\n", p[2].Get()); } else LgiAssert(0); break; } case VcSvn: { if (p.Length() > 2) { ToolVersion[GetType()] = Ver2Int(p[2]); printf("Svn version: %s\n", p[2].Get()); } else LgiAssert(0); break; } case VcHg: { if (p.Length() >= 5) { auto Ver = p[4].Strip("()"); ToolVersion[GetType()] = Ver2Int(Ver); printf("Hg version: %s\n", Ver.Get()); } break; } case VcCvs: { #ifdef _DEBUG for (auto i : p) printf("i='%s'\n", i.Get()); #endif LgiAssert(!"Impl me."); break; } default: break; } return false; } bool VcFolder::ParseAddFile(int Result, GString s, ParseParams *Params) { switch (GetType()) { case VcCvs: { break; } default: break; } return false; } bool VcFolder::AddFile(const char *Path, bool AsBinary) { if (!Path) return false; switch (GetType()) { case VcCvs: { GString a; a.Printf("add%s \"%s\"", AsBinary ? " -kb" : "", Path); return StartCmd(a, &VcFolder::ParseAddFile); break; } default: { LgiAssert(!"Impl me."); break; } } return false; } bool VcFolder::ParseRevert(int Result, GString s, ParseParams *Params) { ListWorkingFolder(); return false; } bool VcFolder::Revert(const char *Path, const char *Revision) { if (!Path) return false; switch (GetType()) { case VcGit: { GString a; a.Printf("checkout \"%s\"", Path); return StartCmd(a, &VcFolder::ParseRevert); break; } case VcSvn: { GString a; a.Printf("revert \"%s\"", Path); return StartCmd(a, &VcFolder::ParseRevert); break; } default: { LgiAssert(!"Impl me."); break; } } return false; } bool VcFolder::ParseResolve(int Result, GString s, ParseParams *Params) { switch (GetType()) { case VcGit: { break; } case VcSvn: case VcHg: case VcCvs: default: { LgiAssert(!"Impl me."); break; } } return true; } bool VcFolder::Resolve(const char *Path) { if (!Path) return false; switch (GetType()) { case VcGit: { GString a; a.Printf("add \"%s\"", Path); return StartCmd(a, &VcFolder::ParseResolve, new ParseParams(Path)); } case VcSvn: case VcHg: case VcCvs: default: { LgiAssert(!"Impl me."); break; } } return false; } bool VcFolder::ParseBlame(int Result, GString s, ParseParams *Params) { new BlameUi(d, GetType(), s); return false; } bool VcFolder::Blame(const char *Path) { if (!Path) return false; switch (GetType()) { case VcGit: { GString a; a.Printf("blame \"%s\"", Path); return StartCmd(a, &VcFolder::ParseBlame); break; } case VcHg: { GString a; a.Printf("annotate -un \"%s\"", Path); return StartCmd(a, &VcFolder::ParseBlame); break; } case VcSvn: { GString a; a.Printf("blame \"%s\"", Path); return StartCmd(a, &VcFolder::ParseBlame); break; } default: { LgiAssert(!"Impl me."); break; } } return true; } bool VcFolder::SaveFileAs(const char *Path, const char *Revision) { if (!Path || !Revision) return false; return true; } bool VcFolder::ParseSaveAs(int Result, GString s, ParseParams *Params) { return false; } bool VcFolder::ParseCounts(int Result, GString s, ParseParams *Params) { switch (GetType()) { case VcGit: { Unpushed = (int) s.Strip().Split("\n").Length(); break; } case VcSvn: { int64 ServerRev = 0; bool HasUpdate = false; GString::Array c = s.Split("\n"); for (unsigned i=0; i 1 && a[0].Equals("Status")) ServerRev = a.Last().Int(); else if (a[0].Equals("*")) HasUpdate = true; } if (ServerRev > 0 && HasUpdate) { int64 CurRev = CurrentCommit.Int(); Unpulled = (int) (ServerRev - CurRev); } else Unpulled = 0; Update(); break; } default: { LgiAssert(!"Impl me."); break; } } IsUpdatingCounts = false; Update(); return false; // No re-select } void VcFolder::SetEol(const char *Path, int Type) { if (!Path) return; switch (Type) { case IDM_EOL_LF: { ConvertEol(Path, false); break; } case IDM_EOL_CRLF: { ConvertEol(Path, true); break; } case IDM_EOL_AUTO: { #ifdef WINDOWS ConvertEol(Path, true); #else ConvertEol(Path, false); #endif break; } } } void VcFolder::UncommitedItem::Select(bool b) { LListItem::Select(b); if (b) { GTreeItem *i = d->Tree->Selection(); VcFolder *f = dynamic_cast(i); if (f) f->ListWorkingFolder(); if (d->Msg) { d->Msg->Name(NULL); GWindow *w = d->Msg->GetWindow(); if (w) { w->SetCtrlEnabled(IDC_COMMIT, true); w->SetCtrlEnabled(IDC_COMMIT_AND_PUSH, true); } } } } void VcFolder::UncommitedItem::OnPaint(GItem::ItemPaintCtx &Ctx) { GFont *f = GetList()->GetFont(); f->Transparent(false); f->Colour(Ctx.Fore, Ctx.Back); GDisplayString ds(f, "(working folder)"); ds.Draw(Ctx.pDC, Ctx.x1 + ((Ctx.X() - ds.X()) / 2), Ctx.y1 + ((Ctx.Y() - ds.Y()) / 2), &Ctx); } ////////////////////////////////////////////////////////////////////////////////////////// VcLeaf::VcLeaf(VcFolder *parent, GTreeItem *Item, GString path, GString leaf, bool folder) { Parent = parent; d = Parent->GetPriv(); Path = path; Leaf = leaf; Folder = folder; Tmp = NULL; Item->Insert(this); if (Folder) { Insert(Tmp = new GTreeItem); Tmp->SetText("Loading..."); } } GString VcLeaf::Full() { GFile::Path p(Path); p += Leaf; return p.GetFull(); } void VcLeaf::OnBrowse() { Parent->FolderStatus(Full(), this); } void VcLeaf::AfterBrowse() { LList *Files = d->Files; Files->Empty(); GDirectory Dir; for (int b = Dir.First(Full()); b; b = Dir.Next()) { if (Dir.IsDir()) continue; VcFile *f = new VcFile(d, Parent, NULL); if (f) { // f->SetText(COL_STATE, f->SetText(Dir.GetName(), COL_FILENAME); Files->Insert(f); } } Files->ResizeColumnsToContent(); } void VcLeaf::OnExpand(bool b) { if (Tmp && b) { Tmp->Remove(); DeleteObj(Tmp); GFile::Path p(Path); p += Leaf; Parent->ReadDir(this, p); } } char *VcLeaf::GetText(int Col) { if (Col == 0) return Leaf; return NULL; } int VcLeaf::GetImage(int Flags) { return Folder ? IcoFolder : IcoFile; } int VcLeaf::Compare(VcLeaf *b) { // Sort folders to the top... if (Folder ^ b->Folder) return (int)b->Folder - (int)Folder; // Then alphabetical return Stricmp(Leaf.Get(), b->Leaf.Get()); } bool VcLeaf::Select() { return GTreeItem::Select(); } void VcLeaf::Select(bool b) { GTreeItem::Select(b); if (b) OnBrowse(); } void VcLeaf::OnMouseClick(GMouse &m) { if (m.IsContextMenu()) { GSubMenu s; s.AppendItem("Log", IDM_LOG); s.AppendItem("Blame", IDM_BLAME, !Folder); int Cmd = s.Float(GetTree(), m); switch (Cmd) { case IDM_LOG: { break; } case IDM_BLAME: { Parent->Blame(Full()); break; } } } } diff --git a/Lvc/Src/VcFolder.h b/Lvc/Src/VcFolder.h --- a/Lvc/Src/VcFolder.h +++ b/Lvc/Src/VcFolder.h @@ -1,243 +1,244 @@ #ifndef _VcFolder_h_ #define _VcFolder_h_ #include "GSubProcess.h" class VcLeaf; enum LoggingType { LogNone, // No output from cmd LogNormal, // Output appears as it's available LogSilo, // Output appears after cmd finished (keeps it non-interleaved with other log msgs) }; enum LvcError { ErrNone, ErrSubProcessFailed = GSUBPROCESS_ERROR, }; class ReaderThread : public LThread { GStream *Out; GSubProcess *Process; public: ReaderThread(GSubProcess *p, GStream *out); ~ReaderThread(); int Main(); }; extern int Ver2Int(GString v); extern int ToolVersion[VcMax]; class VcFolder : public GTreeItem, public GCss { struct ParseParams { GString Str; GString AltInitPath; VcLeaf *Leaf; bool IsWorking; ParseParams(const char *str = NULL) { Str = str; Leaf = NULL; IsWorking = false; } }; typedef bool (VcFolder::*ParseFn)(int, GString, ParseParams*); class Cmd : public GStream { GString::Array Context; GStringPipe Buf; public: LoggingType Logging; GStream *Log; GAutoPtr Rd; ParseFn PostOp; GAutoPtr Params; LvcError Err; Cmd(GString::Array &context, LoggingType logging, GStream *log) { Context = context; Logging = logging; Log = log; Err = ErrNone; } GString GetBuf() { GString s = Buf.NewGStr(); if (Log && Logging == LogSilo) { GString m; m.Printf("=== %s ===\n\t%s %s\n", Context[0].Get(), Context[1].Get(), Context[2].Get()); Log->Write(m.Get(), m.Length()); auto Lines = s.Split("\n"); for (auto Ln : Lines) Log->Print("\t%s\n", Ln.Get()); } return s; } ssize_t Write(const void *Ptr, ssize_t Size, int Flags = 0) { ssize_t Wr = Buf.Write(Ptr, Size, Flags); if (Log && Logging == LogNormal) Log->Write(Ptr, Size, Flags); if (Flags) Err = (LvcError) Flags; return Wr; } }; class UncommitedItem : public LListItem { AppPriv *d; public: UncommitedItem(AppPriv *priv) { d = priv; } void OnPaint(GItem::ItemPaintCtx &Ctx); void Select(bool b); }; AppPriv *d; VersionCtrl Type; GString Path, CurrentCommit, RepoUrl, VcCmd; GArray Log; GString CurrentBranch; GString::Array Branches; GAutoPtr Uncommit; GString Cache, NewRev; bool CommitListDirty; int Unpushed, Unpulled; GString CountCache; GTreeItem *Tmp; int CmdErrors; struct GitCommit { GString::Array Files; GString Msg, Branch; ParseParams *Param; GitCommit() { Param = NULL; } }; GAutoPtr PostAdd; void GitAdd(); GArray Cmds; bool IsLogging, IsGetCur, IsUpdate, IsFilesCmd, IsWorkingFld, IsCommit, IsUpdatingCounts; void Init(AppPriv *priv); const char *GetVcName(); bool StartCmd(const char *Args, ParseFn Parser = NULL, ParseParams *Params = NULL, LoggingType Logging = LogNone); void OnBranchesChange(); void OnCmdError(GString Output, const char *Msg); void ClearError(); void OnChange(PropType Prop) { Update(); } VcFile *FindFile(const char *Path); bool ParseDiffs(GString s, GString Rev, bool IsWorking); + bool ParseRevList(int Result, GString s, ParseParams *Params); bool ParseLog(int Result, GString s, ParseParams *Params); bool ParseInfo(int Result, GString s, ParseParams *Params); bool ParseFiles(int Result, GString s, ParseParams *Params); bool ParseWorking(int Result, GString s, ParseParams *Params); bool ParseUpdate(int Result, GString s, ParseParams *Params); bool ParseCommit(int Result, GString s, ParseParams *Params); bool ParseGitAdd(int Result, GString s, ParseParams *Params); bool ParsePush(int Result, GString s, ParseParams *Params); bool ParsePull(int Result, GString s, ParseParams *Params); bool ParseCounts(int Result, GString s, ParseParams *Params); bool ParseRevert(int Result, GString s, ParseParams *Params); bool ParseResolve(int Result, GString s, ParseParams *Params); bool ParseBlame(int Result, GString s, ParseParams *Params); bool ParseSaveAs(int Result, GString s, ParseParams *Params); bool ParseBranches(int Result, GString s, ParseParams *Params); bool ParseStatus(int Result, GString s, ParseParams *Params); bool ParseAddFile(int Result, GString s, ParseParams *Params); bool ParseVersion(int Result, GString s, ParseParams *Params); bool ParseClean(int Result, GString s, ParseParams *Params); bool ParseDiff(int Result, GString s, ParseParams *Params); public: VcFolder(AppPriv *priv, const char *p); VcFolder(AppPriv *priv, GXmlTag *t); VersionCtrl GetType(); AppPriv *GetPriv() { return d; } const char *GetPath() { return Path; } char *GetText(int Col); bool Serialize(GXmlTag *t, bool Write); GXmlTag *Save(); void Select(bool b); void ListCommit(VcCommit *c); void ListWorkingFolder(); void FolderStatus(const char *Path = NULL, VcLeaf *Notify = NULL); void Commit(const char *Msg, const char *Branch, bool AndPush); void Push(); void Pull(LoggingType Logging = LogNormal); void Clean(); bool Revert(const char *Path, const char *Revision = NULL); bool Resolve(const char *Path); bool AddFile(const char *Path, bool AsBinary = true); bool Blame(const char *Path); bool SaveFileAs(const char *Path, const char *Revision); void ReadDir(GTreeItem *Parent, const char *Path); void SetEol(const char *Path, int Type); void GetVersion(); void Diff(VcFile *file); void OnPulse(); void OnUpdate(const char *Rev); void OnMouseClick(GMouse &m); void OnRemove(); void OnExpand(bool b); void OnPaint(ItemPaintCtx &Ctx); }; class VcLeaf : public GTreeItem { AppPriv *d; VcFolder *Parent; bool Folder; GString Path, Leaf; GTreeItem *Tmp; public: VcLeaf(VcFolder *parent, GTreeItem *Item, GString path, GString leaf, bool folder); GString Full(); void OnBrowse(); void AfterBrowse(); void OnExpand(bool b); char *GetText(int Col); int GetImage(int Flags); int Compare(VcLeaf *b); bool Select(); void Select(bool b); void OnMouseClick(GMouse &m); }; #endif \ No newline at end of file diff --git a/include/common/Emoji.h b/include/common/Emoji.h --- a/include/common/Emoji.h +++ b/include/common/Emoji.h @@ -1,19 +1,19 @@ /// \file /// \brief Softbank emoji access. This file goes with the emoji glyph image /// generated by another application. It maps the glyphs to a grid /// where a direct relationship between character and x,y location of /// the associated glyph is established. #ifndef _EMOJI_H_ #define _EMOJI_H_ /// Size of the graphics cells #define EMOJI_CELL_SIZE 20 /// The width of each group in cells #define EMOJI_GROUP_X 16 /// The height of each group in cells #define EMOJI_GROUP_Y 6 -extern int EmojiToIconIndex(const uint32 *Str, ssize_t Len); +extern int EmojiToIconIndex(const uint32_t *Str, ssize_t Len); #endif \ No newline at end of file diff --git a/include/common/GHtmlCommon.h b/include/common/GHtmlCommon.h --- a/include/common/GHtmlCommon.h +++ b/include/common/GHtmlCommon.h @@ -1,211 +1,211 @@ #ifndef _GHTMLSTATIC_H_ #define _GHTMLSTATIC_H_ #include "GCss.h" #include "GHashTable.h" extern char16 GHtmlListItem[]; #define SkipWhiteSpace(s) while (*s && IsWhiteSpace(*s)) s++; enum HtmlTag { CONTENT, CONDITIONAL, ROOT, TAG_UNKNOWN, TAG_HTML, TAG_HEAD, TAG_BODY, TAG_B, TAG_I, TAG_U, TAG_P, TAG_BR, TAG_UL, TAG_OL, TAG_LI, TAG_FONT, TAG_A, TAG_TABLE, TAG_TR, TAG_TD, TAG_TH, TAG_IMG, TAG_DIV, TAG_SPAN, TAG_CENTER, TAG_META, TAG_TBODY, TAG_STYLE, TAG_SCRIPT, TAG_STRONG, TAG_BLOCKQUOTE, TAG_PRE, TAG_H1, TAG_H2, TAG_H3, TAG_H4, TAG_H5, TAG_H6, TAG_HR, TAG_IFRAME, TAG_LINK, TAG_BIG, TAG_INPUT, TAG_SELECT, TAG_LABEL, TAG_FORM, TAG_NOSCRIPT, TAG_LAST }; /// Common element info struct GHtmlElemInfo { public: enum InfoFlags { TI_NONE = 0x00, TI_NEVER_CLOSES = 0x01, TI_NO_TEXT = 0x02, TI_BLOCK = 0x04, TI_TABLE = 0x08, TI_SINGLETON = 0x10, }; HtmlTag Id; const char *Tag; bool Reattach; int Flags; bool NeverCloses() { return TestFlag(Flags, TI_NEVER_CLOSES); } bool NoText() { return TestFlag(Flags, TI_NO_TEXT); } bool Block() { return TestFlag(Flags, TI_BLOCK); } }; /// Common data for HTML related classes class GHtmlStatic { friend class GHtmlStaticInst; GHtmlElemInfo *UnknownElement; LHashTbl,GHtmlElemInfo*> TagMap; LHashTbl,GHtmlElemInfo*> TagIdMap; public: static GHtmlStatic *Inst; int Refs; - LHashTbl,uint32> VarMap; + LHashTbl,uint32_t> VarMap; LHashTbl,GCss::PropType> StyleMap; LHashTbl,int> ColourMap; GHtmlStatic(); ~GHtmlStatic(); GHtmlElemInfo *GetTagInfo(const char *Tag); GHtmlElemInfo *GetTagInfo(HtmlTag TagId); }; /// Static data setup/pulldown class GHtmlStaticInst { public: GHtmlStatic *Static; GHtmlStaticInst() { if (!GHtmlStatic::Inst) { GHtmlStatic::Inst = new GHtmlStatic; } if (GHtmlStatic::Inst) { GHtmlStatic::Inst->Refs++; } Static = GHtmlStatic::Inst; } ~GHtmlStaticInst() { if (GHtmlStatic::Inst) { GHtmlStatic::Inst->Refs--; if (GHtmlStatic::Inst->Refs == 0) { DeleteObj(GHtmlStatic::Inst); } } } }; class GCssStyle : public GDom { public: GCss *Css; GCssStyle() { Css = NULL; } bool GetVariant(const char *Name, GVariant &Value, char *Array = 0); bool SetVariant(const char *Name, GVariant &Value, char *Array = 0); }; /// Common base class for a HTML element class GHtmlElement : public GDom, public GCss { friend class GHtmlParser; friend class HtmlEdit; protected: GAutoWString Txt; - uint8 WasClosed : 1; + uint8_t WasClosed : 1; GCssStyle StyleDom; public: HtmlTag TagId; GAutoString Tag; GHtmlElemInfo *Info; GAutoString Condition; GHtmlElement *Parent; GArray Children; GHtmlElement(GHtmlElement *parent); ~GHtmlElement(); // Methods char16 *GetText() { return Txt; } // Heirarchy bool Attach(GHtmlElement *Child, ssize_t Idx = -1); void Detach(); bool HasChild(GHtmlElement *Child); // Virtuals virtual bool Get(const char *attr, const char *&val) { return false; } virtual void Set(const char *attr, const char *val) {} virtual void SetStyle() {} virtual GAutoString DescribeElement() { return GAutoString(); } // Helper void Set(const char *attr, const char16 *val) { GAutoString utf8(WideToUtf8(val)); Set(attr, utf8); } #ifdef _DEBUG bool Debug() { const char *sDebug = NULL; if (Get("debug", sDebug)) return atoi(sDebug) != 0; return false; } #endif }; #endif \ No newline at end of file diff --git a/include/common/GHtmlPriv.h b/include/common/GHtmlPriv.h --- a/include/common/GHtmlPriv.h +++ b/include/common/GHtmlPriv.h @@ -1,463 +1,463 @@ #ifndef _GHTML1_PRIV_H_ #define _GHTML1_PRIV_H_ #include "GCss.h" #include "GToken.h" #include "GHtmlParser.h" class HtmlEdit; namespace Html1 { #define DefaultTextColour Rgb32(0, 0, 0) ////////////////////////////////////////////////////////////////////////////////// // Structs & Classes // ////////////////////////////////////////////////////////////////////////////////// class GFlowRect; class GFlowRegion; #define ToTag(t) dynamic_cast(t) struct GTagHit { GTag *Direct; // Tag directly under cursor GTag *NearestText; // Nearest tag with text int Near; // How close in px was the position to NearestText. // 0 if a direct hit, >0 is near miss, -1 if invalid. bool NearSameRow; // True if 'NearestText' on the same row as click. GdcPt2 LocalCoords; // The position in local co-ords of the tag GFlowRect *Block; // Text block hit ssize_t Index; // If Block!=NULL then index into text, otherwise -1. GTagHit() { Direct = NULL; NearestText = NULL; NearSameRow = false; Block = 0; Near = -1; Index = -1; LocalCoords.x = LocalCoords.y = -1; } void Dump(const char *Desc); }; class GLength { protected: float d; float PrevAbs; GCss::LengthType u; public: GLength(); GLength(char *s); bool IsValid(); bool IsDynamic(); float GetPrevAbs() { return PrevAbs; } operator float(); GLength &operator =(float val); GCss::LengthType GetUnits(); void Set(char *s); float Get(GFlowRegion *Flow, GFont *Font, bool Lock = false); float GetRaw() { return d; } }; class GLine : public GLength { public: int LineStyle; int LineReset; GCss::ColorDef Colour; GLine(); ~GLine(); GLine &operator =(int i); void Set(char *s); }; class GFlowRect : public GRect { public: GTag *Tag; char16 *Text; ssize_t Len; GFlowRect() { Tag = 0; Text = 0; Len = 0; } ~GFlowRect() { } int Start(); bool OverlapX(int x) { return x >= x1 && x <= x2; } bool OverlapY(int y) { return y >= y1 && y <= y2; } bool OverlapX(GFlowRect *b) { return !(b->x2 < x1 || b->x1 > x2); } bool OverlapY(GFlowRect *b) { return !(b->y2 < y1 || b->y1 > y2); } }; class GArea : public GArray { public: ~GArea(); void Empty() { DeleteObjects(); } GRect Bounds(); GRect *TopRect(GRegion *c); void FlowText(GTag *Tag, GFlowRegion *c, GFont *Font, int LineHeight, char16 *Text, GCss::LengthType Align); }; struct GHtmlTableLayout { typedef GArray CellArray; GArray c; GTag *Table; GdcPt2 s; GCss::Len TableWidth; // Various pixels sizes int AvailableX; int CellSpacing; int BorderX1, BorderX2; GRect TableBorder, TablePadding; // in Px // The col and row sizes GArray MinCol, MaxCol, MaxRow; GArray SizeCol; GHtmlTableLayout(GTag *table); void GetSize(int &x, int &y); void GetAll(List &All); GTag *Get(int x, int y); bool Set(GTag *t); int GetTotalX(int StartCol = 0, int Cols = -1); void AllocatePx(int StartCol, int Cols, int MinPx, bool FillWidth); void DeallocatePx(int StartCol, int Cols, int MaxPx); void LayoutTable(GFlowRegion *f, uint16 Depth); void Dump(); }; class GTag : public GHtmlElement { friend struct GHtmlTableLayout; friend class ::HtmlEdit; public: enum HtmlControlType { CtrlNone, CtrlPassword, CtrlEmail, CtrlText, CtrlButton, CtrlSubmit, CtrlSelect, CtrlHidden, }; class TextConvertState { GStream *Out; int PrevLineLen; GArray Buf; public: int Depth; int CharsOnLine; TextConvertState(GStream *o) { Out = o; Depth = 0; CharsOnLine = 0; PrevLineLen = 0; } ~TextConvertState() { if (CharsOnLine) NewLine(); } ssize_t _Write(const void *Ptr, ssize_t Bytes) { // Check if we have enough space to store the string.. size_t Total = CharsOnLine + Bytes; if (Buf.Length() < Total) { // Extend the memory buffer if (!Buf.Length(Total + 32)) return -1; } // Store the string into a line buffer memcpy(&Buf[CharsOnLine], Ptr, Bytes); CharsOnLine += Bytes; return Bytes; } ssize_t Write(const void *Ptr, ssize_t Bytes) { char *start = (char*) Ptr, *cur; char *end = start + Bytes; const char *eol = "\r\n"; while (start < end) { for (cur = start; *cur && cur < end && !strchr(eol, *cur); cur++) ; if (!*cur || cur >= end) break; _Write(start, (int) (cur - start)); start = cur; while (*start && start < end && strchr(eol, *start)) start++; } return _Write(start, (int) (end - start)); } int GetPrev() { return PrevLineLen; } void NewLine() { bool Valid = false; - const uint8 Ws[] = {' ', '\t', 0xa0, 0}; + const uint8_t Ws[] = {' ', '\t', 0xa0, 0}; GUtf8Ptr p(&Buf[0]); - uint8 *End = (uint8*) &Buf[CharsOnLine]; + uint8_t *End = (uint8_t*) &Buf[CharsOnLine]; while (p.GetPtr() < End) { if (!strchr((char*)Ws, p)) { Valid = true; break; } p++; } if (!Valid) CharsOnLine = 0; Buf[CharsOnLine] = 0; if (CharsOnLine || PrevLineLen) { Out->Write(&Buf[0], CharsOnLine); Out->Write("\n", 1); PrevLineLen = CharsOnLine; CharsOnLine = 0; } } }; protected: /// A hash table of attributes. /// /// All strings stored in here should be in UTF-8. Each string is allocated on the heap. LHashTbl, char*> Attr; // Forms GViewI *Ctrl; GVariant CtrlValue; HtmlControlType CtrlType; // Text GAutoWString PreTxt; // Debug stuff void _Dump(GStringPipe &Buf, int Depth); void _TraceOpenTags(); // Private methods GFont *NewFont(); ssize_t NearestChar(GFlowRect *Fr, int x, int y); GTag *HasOpenTag(char *t); GTag *PrevTag(); GRect ChildBounds(); bool GetWidthMetrics(GTag *Table, uint16 &Min, uint16 &Max); void LayoutTable(GFlowRegion *f, uint16 Depth); void BoundParents(); bool PeekTag(char *s, char *tag); GTag *GetTable(); char *NextTag(char *s); void ZeroTableElements(); bool OnUnhandledColor(GCss::ColorDef *def, const char *&s); void CenterText(); bool Serialize(GXmlTag *t, bool Write); GColour _Colour(bool Fore); COLOUR GetFore() { return _Colour(true).c24(); } COLOUR GetBack() { return _Colour(false).c24(); } public: // Object GString::Array Class; const char *HtmlId; GAutoString Condition; int TipId; // Heirarchy GHtml *Html; bool IsBlock() { return Display() == GCss::DispBlock; } GTag *GetBlockParent(ssize_t *Idx = 0); GFont *GetFont(); // Style GdcPt2 Pos; GdcPt2 Size; GFont *Font; int LineHeightCache; GRect PadPx; // Images bool ImageResized; GAutoPtr Image; void SetImage(const char *uri, GSurface *i); void LoadImage(const char *Uri); // Load just this URI void LoadImages(); // Recursive load all image URI's void ImageLoaded(char *uri, GSurface *img, int &Used); // Table stuff struct TblCell { GdcPt2 Pos; GdcPt2 Span; GRect BorderPx; GRect PaddingPx; uint16 MinContent, MaxContent; GCss::LengthType XAlign; GHtmlTableLayout *Cells; TblCell() { Cells = NULL; MinContent = 0; MaxContent = 0; XAlign = GCss::LenInherit; BorderPx.ZOff(0, 0); PaddingPx.ZOff(0, 0); } ~TblCell() { DeleteObj(Cells); } } *Cell; #ifdef _DEBUG int Debug; #endif // Text ssize_t Cursor; // index into text of the cursor ssize_t Selection; // index into the text of the selection edge GArea TextPos; GTag(GHtml *h, GHtmlElement *p); ~GTag(); // Events void OnChange(PropType Prop); bool OnClick(); // Attributes bool Get(const char *attr, const char *&val) { val = Attr.Find(attr); return val != 0; } void Set(const char *attr, const char *val); // Methods char16 *Text() { return Txt; } void Text(char16 *t) { Txt.Reset(t); TextPos.Empty(); } char16 *PreText() { return PreTxt; } void PreText(char16 *t) { PreTxt.Reset(t); TextPos.Empty(); } ssize_t GetTextStart(); GAutoWString DumpW(); GAutoString DescribeElement(); char16 *CleanText(const char *s, ssize_t len, const char *SourceCs, bool ConversionAllowed = true, bool KeepWhiteSpace = false); char *ParseText(char *Doc); bool ConvertToText(TextConvertState &State); /// Configures the tag's styles. void SetStyle(); /// Called to apply CSS selectors on initialization and also when properties change at runtime. void Restyle(); /// Recursively call restyle on all nodes in the doc tree void RestyleAll(); /// Takes the CSS styles, parses and stores them in the current object, //// overwriting any duplicate properties. void SetCssStyle(const char *Style); /// Positions the tag according to the flow region passed in void OnFlow(GFlowRegion *Flow, uint16 Depth); /// Paints the border and background of the tag void PaintBorderAndBackground( /// The surface to paint on GSurface *pDC, /// The background colour (transparent is OK) GColour &Back, /// [Optional] The size of the border painted GRect *Px = NULL); /// This fills 'rgn' with all the rectangles making up the inline tags region void GetInlineRegion(GRegion &rgn); void OnPaint(GSurface *pDC, bool &InSelection, uint16 Depth); void SetSize(GdcPt2 &s); void SetTag(const char *Tag); void GetTagByPos(GTagHit &TagHit, int x, int y, int Depth, bool InBody, bool DebugLog = false); GTag *GetTagByName(const char *Name); void CopyClipboard(GMemQueue &p, bool &InSelection); GTag *IsAnchor(GAutoString *Uri); bool CreateSource(GStringPipe &p, int Depth = 0, bool LastWasBlock = true); void Find(int TagType, GArray &Tags); GTag *GetAnchor(char *Name); // Control handling GTag *FindCtrlId(int Id); int OnNotify(int f); void CollectFormValues(LHashTbl,char*> &f); // GDom impl bool GetVariant(const char *Name, GVariant &Value, char *Array = 0); bool SetVariant(const char *Name, GVariant &Value, char *Array = 0); // Window bool OnMouseClick(GMouse &m); void Invalidate(); // Positioning int RelX() { return Pos.x + (int)MarginLeft().Value; } int RelY() { return Pos.y + (int)MarginTop().Value; } GdcPt2 AbsolutePos(); inline int AbsX() { return AbsolutePos().x; } inline int AbsY() { return AbsolutePos().y; } GRect GetRect(bool Client = true); GCss::LengthType GetAlign(bool x); // Tables GTag *GetTableCell(int x, int y); GdcPt2 GetTableSize(); void ResetCaches(); }; } #endif \ No newline at end of file diff --git a/include/common/GTableDb.h b/include/common/GTableDb.h --- a/include/common/GTableDb.h +++ b/include/common/GTableDb.h @@ -1,171 +1,171 @@ #ifndef _GTABLEDB_H #define _GTABLEDB_H #include "GVariant.h" #define ID(c) c class GTableDb : public GDom { struct GTableDbPriv *d; public: class Table; enum HeaderTypes { DbIndexHeader = ID('indx'), DbFieldDef = ID('flds'), DbKeys = ID('keys'), DbRow = ID('data'), DbEmpty = ID('empt'), }; enum FieldFlags { FIELD_SORTED = 0x01, FIELD_AUTONUMBER = 0x02, }; // Riff chunk class Chunk { union { - uint32 IntId; + uint32_t IntId; char StrId[4]; }; - uint32 Size; // If size is zero, the all the remaining data in + uint32_t Size; // If size is zero, the all the remaining data in // the file or memory is part of the chunk. }; struct Base { - bool Io(uint8 &i8, GPointer &p, bool write); + bool Io(uint8_t &i8, GPointer &p, bool write); bool Io(uint16 &i16, GPointer &p, bool write); - bool Io(uint32 &i32, GPointer &p, bool write); + bool Io(uint32_t &i32, GPointer &p, bool write); bool Io(uint64 &i64, GPointer &p, bool write); bool Io(GAutoString &str, GPointer &p, bool write); }; class Field : public Base { public: union { - uint32 Id; + uint32_t Id; char IdStr[4]; }; char Null; - uint8 Type; - uint8 Flags; + uint8_t Type; + uint8_t Flags; GAutoString Name; Field(); Field(int id, int type, const char *name, int flags = 0); void Set(int id, int type, const char *name, int flags = 0); bool Serialize(GPointer &p, bool Write); bool operator !=(const Field &b); Field &operator=(const Field &b); }; typedef GArray Schema; /// \returns TRUE if the schemas are the same. static bool CompareSchema(Schema &a, Schema &b); /// Copies 'src' to 'dest' static bool CopySchema(Schema &dest, Schema &src); struct RowId { int32 BlockId; int32 Offset; bool Empty() { return Offset < 0; } }; class Table : public GBase, public GDom { struct GTablePriv *d; public: Table(GTableDbPriv *db, const char *Name); ~Table(); // Table level operations bool DeleteTable(); bool AddFile(const char *Path, int Number, bool IsIndex); // Field operations bool ChangeSchema(Schema &NewFields); Field *GetField(int FieldId); Field *GetFieldAt(int Index); // Row operations /// Create a new row for a auto-numbered table - RowId NewRow(uint32 *Auto); + RowId NewRow(uint32_t *Auto); /// Create a new row with the specified integer key - RowId NewRow(uint32 Key); + RowId NewRow(uint32_t Key); /// Create a new row with the specified string key RowId NewRow(const char *Key); /// Finds the first matching record for the specified key - RowId SeekKey(uint32 key); + RowId SeekKey(uint32_t key); /// Finds the first matching record for the specified key RowId SeekKey(const char *key); /// Sets an integer value - bool Set(RowId row, uint32 Fld, uint32 i); + bool Set(RowId row, uint32_t Fld, uint32_t i); /// Sets a string value - bool Set(RowId row, uint32 Fld, const char *str); + bool Set(RowId row, uint32_t Fld, const char *str); /// Gets an integer value - uint32 GetInt32(RowId row, uint32 Fld); + uint32_t GetInt32(RowId row, uint32_t Fld); /// Gets a string value - const char *GetStr(RowId row, uint32 Fld); + const char *GetStr(RowId row, uint32_t Fld); }; GTableDb(const char *BaseFolder); ~GTableDb(); /// Open a base folder bool Open(const char *BaseFolder); /// Clear out loaded tables void Empty(); /// Set a log file void SetLogStream(GStream *log); /// An array of available tables GArray &Tables(); /// Get a specific table Table *GetTable(const char *Name); /// Create a new table, or if it already exists makes sure the fields are correct. /// If the table exists and the fields are different, it will be updated to match the /// required format. Table *CreateTable ( /// The name of the table (this forms the first part of the filename) const char *Name, /// The fields the table will have. If it already exists the fields will /// be made to match this initial schema. const Schema &Sch ); /// This function flushes all writes to the disk. bool Commit(); /// \returns true if the DB is good condition bool IsOk(); }; extern bool RunTableDbTest(GStream *Log); #endif \ No newline at end of file diff --git a/include/common/GTextFile.h b/include/common/GTextFile.h --- a/include/common/GTextFile.h +++ b/include/common/GTextFile.h @@ -1,311 +1,311 @@ #ifndef _TEXT_FILE_H_ #define _TEXT_FILE_H_ class GTextFile : public GFile { public: enum EncodingType { Unknown, Utf8, Utf16BE, Utf16LE, Utf32BE, Utf32LE, }; protected: bool First; size_t Used; bool InEndOfLine; GPointer Pos; EncodingType Type; GArray Buf; GAutoString Charset; public: GTextFile(const char *charset = NULL) { First = true; InEndOfLine = false; Used = 0; Pos.u8 = NULL; Type = Unknown; if (charset) Charset.Reset(NewStr(charset)); } EncodingType GetType() { return Type; } const char *GetTypeString() { switch (Type) { case Utf8: return "utf-8"; case Utf16BE: return "utf-16be"; case Utf16LE: return "utf-16"; case Utf32BE: return "utf-32be"; case Utf32LE: return "utf-32"; default: break; } return NULL; } bool GetVariant(const char *Name, GVariant &Value, char *Array = NULL) { if (LgiStringToDomProp(Name) == FileEncoding) { Value = (int)Type; return true; } return GFile::GetVariant(Name, Value, Array); } /// Read the whole file as utf-8 GAutoString Read() { GAutoString Ret; int64 Sz = GetSize(); if (Sz > 0) { GAutoPtr Buf(new uint8_t[Sz]); if (Buf) { ssize_t Rd = Read(Buf, (ssize_t)Sz); if (Rd > 0) { const char *Cs = GetTypeString(); if (Cs) { // printf("Text file read: %s\n", GetName()); Ret.Reset((char*)LgiNewConvertCp("utf-8", Buf, Cs, Rd)); } } } } return Ret; } /// Read the whole file as wchar_t GAutoWString ReadW() { GAutoWString Ret; int Sz = (int)GetSize(); if (Sz > 0) { GAutoPtr Buf(new uint8_t[Sz]); if (Buf) { ssize_t Rd = Read(Buf, Sz); if (Rd > 0) { const char *Cs = GetTypeString(); if (Cs) Ret.Reset((char16*)LgiNewConvertCp(LGI_WideCharset, Buf, Cs, Sz)); } } } return Ret; } template bool CheckForNull(T *ptr, uint8_t *end) { while ((uint8_t*)ptr < (end-sizeof(T))) { if (*ptr == 0) return false; ptr++; } return true; } ssize_t Read(void *Buffer, ssize_t Size, int Flags = 0) { ssize_t Rd = GFile::Read(Buffer, Size, Flags); if (First) { if (Rd < 4) LgiAssert(!"Initial read is too small"); else { First = false; uint8_t *buf = (uint8_t*)Buffer; uint8_t *start = buf; if (Used > 2 && buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF) { Type = Utf8; start += 3; } else if (Used > 1 && buf[0] == 0xFE && buf[1] == 0xFF) { Type = Utf16BE; start += 2; } else if (Used > 1 && buf[0] == 0xFF && buf[1] == 0xFE) { Type = Utf16LE; start += 2; } else if (Used > 3 && buf[0] == 0x00 && buf[1] == 0x00 && buf[2] == 0xFE && buf[3] == 0xFF) { Type = Utf32BE; start += 4; } else if (Used > 3 && buf[0] == 0xFF && buf[1] == 0xFE && buf[2] == 0x00 && buf[3] == 0x00) { Type = Utf32LE; start += 4; } else { // Try and detect the char type uint8_t *end = buf + Rd; if (CheckForNull(buf, end)) Type = Utf8; else if (CheckForNull((uint16*)buf, end)) Type = Utf16LE; else Type = Utf32LE; } if (start > buf) { ssize_t bytes = start - buf; if (bytes <= Rd) { // Remove byte order mark from the buffer memmove(buf, start, Rd - bytes); Rd -= bytes; } } } } return Rd; } bool FillBuffer() { if (!Buf.Length()) { Buf.Length(4 << 10); Pos.u8 = &Buf[0]; Used = 0; } if (Buf.Length()) { // Move any consumed data down to the start of the buffer size_t BytePos = Pos.u8 - &Buf[0]; size_t Remaining = Used - BytePos; if (BytePos > 0 && Remaining > 0) { memmove(&Buf[0], &Buf[BytePos], Remaining); Used = Remaining; } else { Used = 0; } Pos.u8 = &Buf[0]; // Now read some more data into the free space Remaining = Buf.Length() - Used; LgiAssert(Remaining > 0); ssize_t Rd = Read(&Buf[Used], Remaining); if (Rd <= 0) return 0; Used += Rd; } return true; } template int GetLine(GArray &Out) { - uint8 *End = NULL; + uint8_t *End = NULL; int OutPos = 0; if (Buf.Length()) End = &Buf[0] + Used; while (true) { if (!End || End - Pos.u8 < sizeof(T)) { if (!FillBuffer()) break; End = &Buf[0] + Used; } if (End - Pos.u8 < sizeof(T)) break; T ch = 0; switch (Type) { case Utf16BE: LgiAssert(sizeof(ch) == 2); ch = LgiSwap16(*Pos.u16); Pos.u16++; break; case Utf16LE: LgiAssert(sizeof(ch) == 2); ch = *Pos.u16++; break; case Utf32BE: LgiAssert(sizeof(ch) == 4); ch = LgiSwap32(*Pos.u32); Pos.u32++; break; case Utf32LE: LgiAssert(sizeof(ch) == 4); ch = *Pos.u32++; break; default: // Utf8 { ssize_t len = (int) (End - Pos.u8); ch = LgiUtf8To32(Pos.u8, len); break; } } if (ch == 0) break; if (ch == '\r' || ch == '\n') { if (InEndOfLine) { continue; } else { InEndOfLine = true; break; } } else { InEndOfLine = false; } Out[OutPos++] = ch; } Out[OutPos] = 0; // NULL terminate return OutPos; } }; #endif \ No newline at end of file diff --git a/include/common/LParsePart.h b/include/common/LParsePart.h --- a/include/common/LParsePart.h +++ b/include/common/LParsePart.h @@ -1,121 +1,121 @@ #ifndef _LPARSE_PART_H_ #define _LPARSE_PART_H_ // Very simple variable parser // See fn 'UnitTests' for examples struct LParsePart { // The character that started the variable or 0 for a literal string char Start; // if (Start != 0) -> The name of the parsed variable // else -> The string literal GString Str; LParsePart() { Start = 0; } static GArray Split ( // String to parse const char *In, // Pairs of characters to match. The space ' ' char // can be used as a special end marker that just means // parse till the end of the word. // // e.g. "%%" would parse things like "An environment %variable% to match" // "[]" would parse an array marker: "some_var[index]" // "$ " would parse this: "here is my $variable to match" const char *SeparatorPairs ) { GArray a; const char *c = In; const char *prev = c; char Lut[256] = {0}; for (auto p = SeparatorPairs; p[0] && p[1]; p += 2) Lut[p[0]] = p[1]; #ifdef _DEBUG const char *End = c + strlen(c); #endif while (c && *c) { #ifdef _DEBUG if (c >= End) LgiAssert(!"Off the end"); #endif - char End = Lut[(uint8)*c]; + char End = Lut[(uint8_t)*c]; if (End) { if (c > prev) a.New().Str.Set(prev, c - prev); auto &v = a.New(); v.Start = *c++; prev = c; if (End == ' ') { // Word match while (*c && (IsAlpha(*c) || IsDigit(*c) || *c == '_')) c++; v.Str.Set(prev, c - prev); prev = c; } else { // End char match while (*c && *c != End) c++; v.Str.Set(prev, c - prev); if (*c) c++; prev = c; } } else c++; } if (c > prev) a.New().Str.Set(prev, c - prev); return a; } #undef FAILTEST #define FAILTEST(test, str) if (!(test)) { LgiAssert(!str); return false; } bool Check(char start, const char *str) { return Start == start && Str.Equals(str); } static bool UnitTests() { // Non word based auto a = Split("arraytest[123];", "[]"); FAILTEST(a.Length() == 3, "arr len") FAILTEST(a[0].Check(0, "arraytest"), "arr[0]") FAILTEST(a[1].Check('[', "123"), "arr[1]") FAILTEST(a[2].Check(0, ";"), "arr[1]") // Unterminated non-word a = Split("arraytest[12", "[]"); FAILTEST(a.Length() == 2, "arr len") FAILTEST(a[0].Check(0, "arraytest"), "arr[0]") FAILTEST(a[1].Check('[', "12"), "arr[1]") // Word based a = Split("some $value to parse", "$ "); FAILTEST(a.Length() == 3, "arr len") FAILTEST(a[0].Check(0, "some "), "arr[0]") FAILTEST(a[1].Check('$', "value"), "arr[1]") FAILTEST(a[2].Check(0, " to parse"), "arr[2]") // Unterminated word based a = Split("some $valu", "$ "); FAILTEST(a.Length() == 2, "arr len") FAILTEST(a[0].Check(0, "some "), "arr[0]") FAILTEST(a[1].Check('$', "valu"), "arr[1]") return true; } }; #endif \ No newline at end of file diff --git a/include/common/LSerialize.h b/include/common/LSerialize.h --- a/include/common/LSerialize.h +++ b/include/common/LSerialize.h @@ -1,520 +1,520 @@ #ifndef _SERIALIZE_H_ #define _SERIALIZE_H_ enum LSTypes { LInt, LString, LFloat, LObject, LBinary, }; class LSerialize { #ifdef WIN32 #pragma pack(push, before_pack) #pragma pack(1) #endif struct Str { - uint32 Length; + uint32_t Length; union { char u[1]; char16 w[1]; }; }; struct Field { - uint8 Type; - uint8 Size; + uint8_t Type; + uint8_t Size; uint16 Id; union { char bytes[1]; Str s; - uint8 u8; + uint8_t u8; uint16 u16; - uint32 u32; + uint32_t u32; uint64 u64; float f; double d; }; bool IsValid() { switch (Type) { case LInt: switch (Size) { case 1: case 2: case 4: case 8: return true; } return false; case LString: if (Size == 1) return s.u[s.Length] == 0; else if (Size == sizeof(char16)) return s.w[s.Length] == 0; return false; case LBinary: if (Size != 1) return false; return true; case LFloat: return Size == sizeof(f); case LObject: return true; } return false; } - uint32 Sizeof() + uint32_t Sizeof() { switch (Type) { case LInt: case LFloat: return 4 + Size; case LString: return 8 + (Size * (s.Length + 1)); case LObject: return 4 + u32; case LBinary: return 8 + s.Length; } return sizeof(*this); } } #ifndef WIN32 __attribute__((packed)) #endif ; #ifdef WIN32 #pragma pack(pop, before_pack) #endif uint16 ObjectId; bool ToStream; GStreamI *Stream; GArray FieldMem; LHashTbl, ssize_t> Fields; - uint32 Bytes; + uint32_t Bytes; protected: Field *GetField(int Id) { ssize_t o = Fields.Find(Id); if (o >= 0) return (Field*)FieldMem.AddressOf(o); return NULL; } template bool Int(uint16 Id, T &i) { if (ToStream) { Field f; f.Type = LInt; f.Id = Id; f.Size = sizeof(i); int Bytes = 4 + f.Size; memcpy(f.bytes, &i, sizeof(i)); if (Stream->Write(&f, Bytes) != Bytes) return false; } else { Field *f = GetField(Id); if (!f) return false; switch (f->Size) { case 1: i = (T)f->u8; break; case 2: i = (T)f->u16; break; case 4: i = (T)f->u32; break; case 8: i = (T)f->u64; break; default: return false; } } return true; } bool String(uint16 Id, GString &s) { if (ToStream) { Field f; f.Type = LString; f.Size = 1; f.Id = Id; - f.s.Length = (uint32)s.Length(); + f.s.Length = (uint32_t)s.Length(); ssize_t bytes = (char*)&f.s.u - (char*)&f; if (Stream->Write(&f, bytes) != bytes) return false; if (Stream->Write(s.Get(), s.Length()) != s.Length()) return false; } else { Field *f = GetField(Id); if (!f || f->Type != LString) return false; return s.Set(f->s.u, f->s.Length); } return false; } Field *Alloc(int Id, size_t Sz) { Field *f = GetField(Id); if (f && f->Sizeof() >= Sz) return f; size_t Off = FieldMem.Length(); FieldMem.Length(Off + Sz); f = (Field*) FieldMem.AddressOf(Off); f->Id = Id; Fields.Add(Id, Off); return f; } public: LSerialize(uint16 ObjId = 0) : Fields(64,-1) { ObjectId = ObjId; ToStream = true; Stream = NULL; } virtual ~LSerialize() { } uint16 GetObjectId() { return ObjectId; } size_t GetSize() { size_t Sz = 0; // for (ssize_t o = Fields.First(); o >= 0; o = Fields.Next()) for (auto o : Fields) { Field *i = (Field*)FieldMem.AddressOf(o.value); Sz += i->Sizeof(); } return Sz; } void Empty() { ObjectId = 0; Fields.Empty(); FieldMem.Length(0); } int64 GetInt(int Id, int Default = -1) { Field *f = GetField(Id); if (!f || f->Type == LObject) return Default; switch (f->Type) { case LInt: switch (f->Size) { case 1: return f->u8; case 2: return f->u16; case 4: return f->u32; case 8: return f->u64; } case LFloat: return (int)f->f; case LString: if (f->Size == 1) return atoi(f->s.u); else if (f->Size == 2) return AtoiW(f->s.w); else LgiAssert(!"Invalid string size"); break; default: LgiAssert(!"Invalid type."); } return Default; } float GetFloat(int Id, float Default = 0.0) { Field *f = GetField(Id); if (!f || f->Type == LObject) return Default; switch (f->Type) { case LInt: switch (f->Size) { case 1: return (float)f->u8; case 2: return (float)f->u16; case 4: return (float)f->u32; case 8: return (float)f->u64; } case LFloat: if (f->Size == sizeof(float)) return f->f; else if (f->Size == sizeof(double)) return (float)f->d; else LgiAssert(!"Invalid size."); break; case LString: if (f->Size == 1) return (float)atof(f->s.u); else if (f->Size == 2) { GString s(f->s.w); return (float)s.Float(); } else LgiAssert(!"Invalid string size"); break; default: LgiAssert(!"Invalid type."); break; } return Default; } const char *GetStr(int Id, const char *Default = NULL) { Field *f = GetField(Id); if (!f || f->Type != LString) return Default; if (f->Size == 1) return f->s.u; else LgiAssert(!"Request for wrong string width"); return Default; } const char16 *GetStrW(int Id, const char16 *Default = NULL) { Field *f = GetField(Id); if (!f || f->Type != LString) return Default; if (f->Size == 2) return f->s.w; else LgiAssert(!"Request for wrong string width"); return Default; } bool SetBinary(int Id, void *Ptr, ssize_t Size) { Field *f = Alloc(Id, 8 + Size); if (!f) return false; f->Type = LBinary; f->Size = 1; - f->s.Length = (uint32) Size; + f->s.Length = (uint32_t) Size; if (Ptr && Size > 0) memcpy(f->s.u, Ptr, Size); return true; } - bool GetBinary(int Id, uint8 *&Ptr, uint32 &Size) + bool GetBinary(int Id, uint8_t *&Ptr, uint32_t &Size) { Field *f = GetField(Id); if (!f || f->Type != LBinary) return false; - Ptr = (uint8*) f->s.u; + Ptr = (uint8_t*) f->s.u; Size = f->s.Length; #ifdef _DEBUG - uint8 *End = Ptr + Size; - uint8 *Flds = (uint8*)FieldMem.AddressOf(); + uint8_t *End = Ptr + Size; + uint8_t *Flds = (uint8_t*)FieldMem.AddressOf(); LgiAssert(Ptr >= Flds && End <= Flds + FieldMem.Length()); #endif return true; } template bool SetInt(int Id, T i) { Field *f = Alloc(Id, 4 + sizeof(i)); if (!f) return false; f->Type = LInt; f->Size = sizeof(i); memcpy(f->bytes, &i, sizeof(i)); return true; } template bool SetFloat(int Id, T i) { Field *f = Alloc(Id, 4 + sizeof(i)); if (!f) return false; f->Type = LFloat; f->Size = sizeof(i); memcpy(f->bytes, &i, sizeof(i)); return true; } bool SetStr(int Id, const char *u) { if (!u) return false; size_t len = strlen(u) + 1; Field *f = Alloc(Id, 8 + len); if (!f) return false; f->Type = LString; f->Size = 1; - f->s.Length = (uint32) (len - 1); + f->s.Length = (uint32_t) (len - 1); memcpy(f->s.u, u, len); return true; } bool SetStr(int Id, const char16 *w) { if (!w) return false; size_t len = Strlen(w) + 1; Field *f = Alloc(Id, 8 + len); if (!f) return false; f->Type = LString; f->Size = sizeof(*w); - f->s.Length = (uint32) (len - 1); + f->s.Length = (uint32_t) (len - 1); memcpy(f->s.u, w, len * sizeof(*w)); return true; } bool CanRead(void *ptr, size_t size) { if (!ptr || size < 8) return false; Field *f = (Field*)ptr; if (f->Type != LObject) return false; if (f->u32 > size) return false; return true; } bool Serialize(GStream *stream, bool write) { if ((ToStream = write)) { - uint32 Size = 0; + uint32_t Size = 0; // for (ssize_t o = Fields.First(); o >= 0; o = Fields.Next()) for (auto o : Fields) { Field *i = (Field*)FieldMem.AddressOf(o.value); Size += i->Sizeof(); } Field f; f.Size = 0; f.Type = LObject; f.Id = GetObjectId(); f.u32 = Size; if (stream->Write(&f, 8) != 8) return false; // for (ssize_t o = Fields.First(); o >= 0; o = Fields.Next()) for (auto o : Fields) { Field *i = (Field*)FieldMem.AddressOf(o.value); int bytes = i->Sizeof(); if (stream->Write(i, bytes) != bytes) return false; } } else { Empty(); Field f; if (stream->Read(&f, 8) != 8) return false; if (f.Type != LObject) return false; ObjectId = f.Id; FieldMem.Length(f.u32); if (stream->Read(FieldMem.AddressOf(), f.u32) != f.u32) return false; // Parse all the fields into a hash table for O(1) access. char *s = FieldMem.AddressOf(); char *e = s + FieldMem.Length(); while (s < e) { Field *fld = (Field*)s; if (!fld->IsValid()) { LgiAssert(!"Invalid field"); return false; } Fields.Add(fld->Id, s - FieldMem.AddressOf(0)); s += fld->Sizeof(); } } return true; } }; #endif diff --git a/src/common/Gdc2/Filters/Gif.cpp b/src/common/Gdc2/Filters/Gif.cpp --- a/src/common/Gdc2/Filters/Gif.cpp +++ b/src/common/Gdc2/Filters/Gif.cpp @@ -1,1051 +1,1051 @@ /*hdr ** FILE: Gif.cpp ** AUTHOR: Matthew Allen ** DATE: 8/9/1998 ** DESCRIPTION: Gif file filter ** ** Copyright (C) 1997-8, Matthew Allen ** fret@memecode.com */ #include #include #include #include "Lgi.h" #include "Lzw.h" #include "GVariant.h" #include "GPalette.h" #ifdef FILTER_UI // define the symbol FILTER_UI to access the gif save options dialog #include "GTransparentDlg.h" #endif #define MAX_CODES 4095 class GdcGif : public GFilter { GSurface *pDC; GStream *s; int ProcessedScanlines; // Old GIF coder stuff short linewidth; int lines; int pass; short curr_size; /* The current code size */ short clearc; /* Value for a clear code */ short ending; /* Value for a ending code */ short newcodes; /* First available code */ short top_slot; /* Highest code for current size */ short slot; /* Last read code */ short navail_bytes; /* # bytes left in block */ short nbits_left; /* # bits left in current byte */ uchar b1; /* Current byte */ uchar byte_buff[257]; /* Current block */ uchar *pbytes; /* Pointer to next byte in block */ uchar stack[MAX_CODES+1]; /* Stack for storing pixels */ uchar suffix[MAX_CODES+1]; /* Suffix table */ ushort prefix[MAX_CODES+1]; /* Prefix linked list */ int bad_code_count; int get_byte(); int out_line(uchar *pixels, int linewidth, int interlaced, int BitDepth); short init_exp(short size); short get_next_code(); short decoder(int BitDepth, uchar interlaced); public: GdcGif(); Format GetFormat() { return FmtGif; } int GetCapabilites() { return FILTER_CAP_READ | FILTER_CAP_WRITE; } IoStatus ReadImage(GSurface *pDC, GStream *In); IoStatus WriteImage(GStream *Out, GSurface *pDC); bool GetVariant(const char *n, GVariant &v, char *a) { if (!_stricmp(n, LGI_FILTER_TYPE)) { v = "Gif"; } else if (!_stricmp(n, LGI_FILTER_EXTENSIONS)) { v = "GIF"; } else return false; return true; } }; // Filter factory // tells the application we're here class GdcGifFactory : public GFilterFactory { bool CheckFile(const char *File, int Access, const uchar *Hint) { if (Hint) { if (Hint[0] == 'G' && Hint[1] == 'I' && Hint[2] == 'F' && Hint[3] == '8' && Hint[4] == '9') { return true; } } return (File) ? stristr(File, ".gif") != 0 : false; } GFilter *NewObject() { return new GdcGif; } } GifFactory; // gif error codes #define OUT_OF_MEMORY -10 #define BAD_CODE_SIZE -20 #define READ_ERROR -1 #define WRITE_ERROR -2 #define OPEN_ERROR -3 #define CREATE_ERROR -4 long code_mask[13] = { 0, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF}; int GdcGif::get_byte() { uchar c; if (s->Read(&c, 1) == 1) return c; return READ_ERROR; } int GdcGif::out_line(uchar *pixels, int linewidth, int interlaced, int BitDepth) { // static int p; // if (lines == 0) p = 0; if (lines >= pDC->Y()) return -1; switch (pDC->GetColourSpace()) { case CsIndex8: { memcpy((*pDC)[lines], pixels, pDC->X()); break; } case CsBgr16: { GBgr16 *s = (GBgr16*) (*pDC)[lines]; GBgr16 *e = s + pDC->X(); GPalette *pal = pDC->Palette(); GdcRGB *p = (*pal)[0], *pix; while (s < e) { pix = p + *pixels++; s->r = pix->r >> 3; s->g = pix->g >> 2; s->b = pix->b >> 3; s++; } break; } case CsRgb16: { GRgb16 *s = (GRgb16*) (*pDC)[lines]; GRgb16 *e = s + pDC->X(); GPalette *pal = pDC->Palette(); GdcRGB *p = (*pal)[0], *pix; while (s < e) { pix = p + *pixels++; s->r = pix->r >> 3; s->g = pix->g >> 2; s->b = pix->b >> 3; s++; } break; } case System32BitColourSpace: { System32BitPixel *s = (System32BitPixel*) (*pDC)[lines]; System32BitPixel *e = s + pDC->X(); GPalette *pal = pDC->Palette(); GdcRGB *p = (*pal)[0], *pix; while (s < e) { pix = p + *pixels++; s->r = pix->r; s->g = pix->g; s->b = pix->b; s->a = 255; s++; } break; } default: { LgiAssert(!"Unsupported colour space"); break; } } ProcessedScanlines++; if (interlaced) { switch (pass) { case 0: lines += 8; if (lines >= pDC->Y()) { lines = 4; pass++; } break; case 1: lines += 8; if (lines >= pDC->Y()) { lines = 2; pass++; } break; case 2: lines += 4; if (lines >= pDC->Y()) { lines = 1; pass++; } break; case 3: lines += 2; break; } } else { lines++; } if (Meter) { int a = (int)Meter->Value() * 100 / pDC->Y(); int b = lines * 100 / pDC->Y(); if (abs(a-b) > 5) { Meter->Value(lines); } } return 0; } short GdcGif::init_exp(short size) { curr_size = size + 1; top_slot = 1 << curr_size; clearc = 1 << size; ending = clearc + 1; slot = newcodes = ending + 1; navail_bytes = nbits_left = 0; return 0; } short GdcGif::get_next_code() { short i, x; ulong ret; if (nbits_left == 0) { if (navail_bytes <= 0) { /* Out of bytes in current block, so read next block */ pbytes = byte_buff; if ((navail_bytes = get_byte()) < 0) { return(navail_bytes); } else if (navail_bytes) { for (i = 0; i < navail_bytes; ++i) { if ((x = get_byte()) < 0) { return(x); } byte_buff[i] = (uchar)x; } } } b1 = *pbytes++; nbits_left = 8; --navail_bytes; } ret = b1 >> (8 - nbits_left); while (curr_size > nbits_left) { if (navail_bytes <= 0) { /* Out of bytes in current block, so read next block */ pbytes = byte_buff; if ((navail_bytes = get_byte()) < 0) { return(navail_bytes); } else if (navail_bytes) { for (i = 0; i < navail_bytes; ++i) { if ((x = get_byte()) < 0) { return(x); } byte_buff[i] = (uchar)x; } } } b1 = *pbytes++; ret |= b1 << nbits_left; nbits_left += 8; --navail_bytes; } nbits_left -= curr_size; ret &= code_mask[curr_size]; return ((short) ret); } short GdcGif::decoder(int BitDepth, uchar interlaced) { uchar *sp, *bufptr; uchar *buf; short code, fc, oc, bufcnt; short c, size, ret; lines = 0; pass = 0; /* Initialize for decoding a new image... */ if ((size = get_byte()) < 0) { return(size); } if (size < 2 || 9 < size) { return(BAD_CODE_SIZE); } init_exp(size); /* Initialize in case they forgot to put in a clearc code. * (This shouldn't happen, but we'll try and decode it anyway...) */ oc = fc = 0; /* Allocate space for the decode buffer */ buf = new uchar[linewidth+1]; if (buf == NULL) { return (OUT_OF_MEMORY); } /* Set up the stack pointer and decode buffer pointer */ uchar *EndOfStack = stack + sizeof(stack); sp = stack; bufptr = buf; bufcnt = linewidth; /* This is the main loop. For each code we get we pass through the * linked list of prefix codes, pushing the corresponding "character" for * each code onto the stack. When the list reaches a single "character" * we push that on the stack too, and then start unstacking each * character for output in the correct order. Special handling is * included for the clearc code, and the whole thing ends when we get * an ending code. */ while ((c = get_next_code()) != ending) { /* If we had a file error, return without completing the decode */ if (c < 0) { DeleteArray(buf); return(0); } /* If the code is a clearc code, reinitialize all necessary items. */ if (c == clearc) { curr_size = size + 1; slot = newcodes; top_slot = 1 << curr_size; /* Continue reading codes until we get a non-clearc code * (Another unlikely, but possible case...) */ while ((c = get_next_code()) == clearc) ; /* If we get an ending code immediately after a clearc code * (Yet another unlikely case), then break out of the loop. */ if (c == ending) { break; } /* Finally, if the code is beyond the range of already set codes, * (This one had better !happen... I have no idea what will * result from this, but I doubt it will look good...) then set it * to color zero. */ if (c >= slot) { c = 0; } oc = fc = c; /* And let us not forget to put the char into the buffer... And * if, on the off chance, we were exactly one pixel from the end * of the line, we have to send the buffer to the out_line() * routine... */ *bufptr++ = (uchar)c; if (--bufcnt == 0) { if ((ret = out_line(buf, linewidth, interlaced, BitDepth)) < 0) { DeleteArray(buf); return (ret); } bufptr = buf; bufcnt = linewidth; } } else { /* In this case, it's not a clearc code or an ending code, so * it must be a code code... So we can now decode the code into * a stack of character codes. (Clear as mud, right?) */ code = c; /* Here we go again with one of those off chances... If, on the * off chance, the code we got is beyond the range of those already * set up (Another thing which had better !happen...) we trick * the decoder into thinking it actually got the last code read. * (Hmmn... I'm not sure why this works... But it does...) */ if (code >= slot) { if (code > slot) { ++bad_code_count; } code = oc; *sp++ = (uchar)fc; } /* Here we scan back along the linked list of prefixes, pushing * helpless characters (ie. suffixes) onto the stack as we do so. */ while (code >= newcodes) { if (sp >= EndOfStack || code >= MAX_CODES + 1) { return -1; } *sp++ = suffix[code]; code = prefix[code]; } /* Push the last character on the stack, and set up the new * prefix and suffix, and if the required slot number is greater * than that allowed by the current bit size, increase the bit * size. (NOTE - If we are all full, we *don't* save the new * suffix and prefix... I'm not certain if this is correct... * it might be more proper to overwrite the last code... */ *sp++ = (uchar)code; if (slot < top_slot) { suffix[slot] = (uchar)(fc = code); prefix[slot++] = oc; oc = c; } if (slot >= top_slot) { if (curr_size < 12) { top_slot <<= 1; ++curr_size; } } /* Now that we've pushed the decoded string (in reverse order) * onto the stack, lets pop it off and put it into our decode * buffer... And when the decode buffer is full, write another * line... */ while (sp > stack) { *bufptr++ = *(--sp); if (--bufcnt == 0) { if ((ret = out_line(buf, linewidth, interlaced, BitDepth)) < 0) { DeleteArray(buf); return(ret); } bufptr = buf; bufcnt = linewidth; } } } } ret = 0; if (bufcnt != linewidth) { ret = out_line(buf, (linewidth - bufcnt), interlaced, BitDepth); } DeleteArray(buf); return(ret); } union LogicalScreenBits { - uint8 u8; + uint8_t u8; struct { - uint8 TableSize : 3; - uint8 SortFlag : 1; - uint8 ColourRes : 3; - uint8 GlobalColorTable : 1; + uint8_t TableSize : 3; + uint8_t SortFlag : 1; + uint8_t ColourRes : 3; + uint8_t GlobalColorTable : 1; }; }; union LocalColourBits { - uint8 u8; + uint8_t u8; struct { - uint8 TableBits : 3; - uint8 Reserved : 2; - uint8 SortFlag : 1; - uint8 Interlaced : 1; - uint8 LocalColorTable : 1; + uint8_t TableBits : 3; + uint8_t Reserved : 2; + uint8_t SortFlag : 1; + uint8_t Interlaced : 1; + uint8_t LocalColorTable : 1; }; }; union GfxCtrlExtBits { - uint8 u8; + uint8_t u8; struct { - uint8 Transparent : 1; - uint8 UserInput : 1; - uint8 DisposalMethod : 3; - uint8 Reserved : 3; + uint8_t Transparent : 1; + uint8_t UserInput : 1; + uint8_t DisposalMethod : 3; + uint8_t Reserved : 3; }; }; bool GifLoadPalette(GStream *s, GSurface *pDC, int TableBits) { GRgb24 Rgb[256]; int Colours = 1 << (TableBits + 1); int Bytes = Colours * sizeof(Rgb[0]); memset(Rgb, 0xFF, sizeof(Rgb)); if (s->Read(Rgb, Bytes) != Bytes) return false; - GPalette *Pal = new GPalette((uint8*)Rgb, 256); + GPalette *Pal = new GPalette((uint8_t*)Rgb, 256); if (!Pal) return false; pDC->Palette(Pal); return true; } GFilter::IoStatus GdcGif::ReadImage(GSurface *pdc, GStream *in) { GFilter::IoStatus Status = IoError; pDC = pdc; s = in; ProcessedScanlines = 0; if (pDC && s) { bad_code_count = 0; if (!FindHeader(0, "GIF8?a", s)) { // not a gif file } else { bool Transparent = false; LogicalScreenBits LogBits; uchar interlace = false; uint16 LogicalX = 0; uint16 LogicalY = 0; - uint8 BackgroundColour = 0; - uint8 PixelAspectRatio = 0; + uint8_t BackgroundColour = 0; + uint8_t PixelAspectRatio = 0; // read header Read(s, &LogicalX, sizeof(LogicalX)); Read(s, &LogicalY, sizeof(LogicalY)); Read(s, &LogBits.u8, sizeof(LogBits.u8)); int Bits = LogBits.ColourRes + 1; Read(s, &BackgroundColour, sizeof(BackgroundColour)); Read(s, &PixelAspectRatio, sizeof(PixelAspectRatio)); if (LogBits.GlobalColorTable) { GifLoadPalette(s, pDC, LogBits.TableSize); } // Start reading the block stream bool Done = false; uchar BlockCode = 0; uchar BlockLabel = 0; uchar BlockSize = 0; while (!Done) { #define Rd(Var) \ if (!Read(s, &Var, sizeof(Var))) \ { \ Done = true; \ break; \ } Rd(BlockCode); switch (BlockCode) { case 0x2C: { // Image Descriptor uint16 x1, y1, sx, sy; LocalColourBits LocalBits; Rd(x1); Rd(y1); Rd(sx); Rd(sy); Rd(LocalBits.u8); linewidth = sx; interlace = LocalBits.Interlaced != 0; if (pDC->Create(sx, sy, CsIndex8)) { if (LocalBits.LocalColorTable) { GifLoadPalette(s, pDC, LocalBits.TableBits); } // Progress if (Meter) { Meter->SetDescription("scanlines"); Meter->SetLimits(0, sy-1); } // Decode image decoder(Bits, interlace); if (ProcessedScanlines == pDC->Y()) Status = IoSuccess; if (Transparent) { // Setup alpha channel pDC->HasAlpha(true); GSurface *Alpha = pDC->AlphaDC(); if (Alpha) { for (int y=0; yY(); y++) { uchar *C = (*pDC)[y]; uchar *A = (*Alpha)[y]; for (int x=0; xX(); x++) { A[x] = C[x] == BackgroundColour ? 0x00 : 0xff; } } } } } else { printf("%s:%i - Failed to create output surface.\n", _FL); } Done = true; break; } case 0x21: { - uint8 GraphicControlLabel; - uint8 BlockSize; + uint8_t GraphicControlLabel; + uint8_t BlockSize; GfxCtrlExtBits ExtBits; uint16 Delay; Rd(GraphicControlLabel); Rd(BlockSize); switch (GraphicControlLabel) { case 0xF9: { Rd(ExtBits.u8); Rd(Delay); Rd(BackgroundColour); Transparent = ExtBits.Transparent != 0; break; } default: { s->SetPos(s->GetPos() + BlockSize); break; } } Rd(BlockSize); while (BlockSize) { int64 NewPos = s->GetPos() + BlockSize; if (s->SetPos(NewPos) != NewPos || !Read(s, &BlockSize, sizeof(BlockSize))) break; } break; } default: { // unknown block Rd(BlockLabel); Rd(BlockSize); while (BlockSize) { int64 NewPos = s->GetPos() + BlockSize; if (s->SetPos(NewPos) != NewPos || !Read(s, &BlockSize, sizeof(BlockSize))) break; } break; } } } } } return Status; } GFilter::IoStatus GdcGif::WriteImage(GStream *Out, GSurface *pDC) { GVariant Transparent; int Back = -1; GVariant v; if (!Out || !pDC) return GFilter::IoError; if (pDC->GetBits() > 8) { if (Props) Props->SetValue(LGI_FILTER_ERROR, v = "The GIF format only supports 1 to 8 bit graphics."); return GFilter::IoUnsupportedFormat; } #ifdef FILTER_UI GVariant Parent; if (Props) { Props->GetValue(LGI_FILTER_PARENT_WND, Parent); if (Props->GetValue(LGI_FILTER_BACKGROUND, v)) { Back = v.CastInt32(); } if (Parent.Type == GV_GVIEW) { // If the source document has an alpha channel then we use // that to create transparent pixels in the output, otherwise // we ask the user if they want the background transparent... if (pDC->AlphaDC()) { Transparent = true; // However we have to pick an unused colour to set as the // "background" pixel value bool Used[256]; ZeroObj(Used); for (int y=0; yY(); y++) { - uint8 *p = (*pDC)[y]; - uint8 *a = (*pDC->AlphaDC())[y]; + uint8_t *p = (*pDC)[y]; + uint8_t *a = (*pDC->AlphaDC())[y]; LgiAssert(p && a); if (!p || !a) break; - uint8 *e = p + pDC->X(); + uint8_t *e = p + pDC->X(); while (p < e) { if (*a) Used[*p] = true; a++; p++; } } Back = -1; for (int i=0; i<256; i++) { if (!Used[i]) { Back = i; break; } } if (Back < 0) { if (Props) Props->SetValue(LGI_FILTER_ERROR, v = "No unused colour for transparent pixels??"); return IoError; } } else { // put up a dialog to ask about transparent colour GTransparentDlg Dlg((GView*)Parent.Value.Ptr, &Transparent); if (!Dlg.DoModal()) { Props->SetValue("Cancel", v = 1); return IoCancel; } } if (Transparent.CastInt32() && Back < 0) { LgiAssert(!"No background colour available??"); if (Props) Props->SetValue(LGI_FILTER_ERROR, v = "Transparency requested, but no background colour set."); return IoError; } } } #endif GPalette *Pal = pDC->Palette(); // Intel byte ordering Out->SetSize(0); // Header Out->Write((void*)"GIF89a", 6); // Logical screen descriptor int16 s = pDC->X(); Write(Out, &s, sizeof(s)); s = pDC->Y(); Write(Out, &s, sizeof(s)); bool Ordered = false; - uint8 c = ((Pal != 0) ? 0x80 : 0) | // global colour table/transparent + uint8_t c = ((Pal != 0) ? 0x80 : 0) | // global colour table/transparent (pDC->GetBits() - 1) | // bits per pixel ((Ordered) ? 0x08 : 0) | // colours are sorted (pDC->GetBits() - 1); Out->Write(&c, 1); c = 0; Out->Write(&c, 1); // background colour c = 0; Out->Write(&c, 1); // aspect ratio // global colour table if (Pal) { uchar Buf[768]; uchar *d = Buf; int Colours = 1 << pDC->GetBits(); for (int i=0; ir; *d++ = s->g; *d++ = s->b; } else { *d++ = i; *d++ = i; *d++ = i; } } Out->Write(Buf, Colours * 3); } if (Transparent.CastInt32()) { // Graphic Control Extension uchar gce[] = {0x21, 0xF9, 4, 1, 0, 0, (uchar)Back, 0 }; Out->Write(gce, sizeof(gce)); } // Image descriptor c = 0x2c; Out->Write(&c, 1); // Image Separator s = 0; Write(Out, &s, sizeof(s)); // Image left position s = 0; Write(Out, &s, sizeof(s)); // Image top position s = pDC->X(); Write(Out, &s, sizeof(s)); // Image width s = pDC->Y(); Write(Out, &s, sizeof(s)); // Image height c = 0; Out->Write(&c, 1); // Flags // Image data c = 8; Out->Write(&c, 1); // Min code size GMemQueue Encode, Pixels; // Get input ready int Len = (pDC->X() * pDC->GetBits() + 7) / 8; - uint8 *buf = pDC->AlphaDC() ? new uint8[Len] : 0; + uint8_t *buf = pDC->AlphaDC() ? new uint8_t[Len] : 0; for (int y=0; yY(); y++) { - uint8 *p = (*pDC)[y]; + uint8_t *p = (*pDC)[y]; if (!p) continue; if (pDC->AlphaDC()) { // Preprocess pixels to make the alpha channel into the // transparent colour. - uint8 *a = (*pDC->AlphaDC())[y]; - uint8 *e = p + pDC->X(); - uint8 *o = buf; + uint8_t *a = (*pDC->AlphaDC())[y]; + uint8_t *e = p + pDC->X(); + uint8_t *o = buf; while (p < e) { if (*a++) *o++ = *p; else *o++ = Back; p++; } LgiAssert(o == buf + Len); p = buf; } Pixels.Write(p, Len); } DeleteArray(buf); // Compress Lzw Encoder; Encoder.Meter = Meter; if (Encoder.Compress(&Encode, &Pixels)) { uchar Buf[256]; // write data out while ((Len = (int)Encode.GetSize()) > 0) { int l = MIN(Len, 255); if (Encode.Read(Buf, l)) { c = l; Out->Write(&c, 1); // Sub block size Out->Write(Buf, l); } } c = 0; Out->Write(&c, 1); // Terminator sub block } // Trailer c = 0x3b; Out->Write(&c, 1); return GFilter::IoSuccess; } GdcGif::GdcGif() { ProcessedScanlines = 0; } diff --git a/src/common/Gdc2/Filters/Jpeg.cpp b/src/common/Gdc2/Filters/Jpeg.cpp --- a/src/common/Gdc2/Filters/Jpeg.cpp +++ b/src/common/Gdc2/Filters/Jpeg.cpp @@ -1,1125 +1,1125 @@ /*hdr ** FILE: Jpeg.cpp ** AUTHOR: Matthew Allen ** DATE: 8/9/1998 ** DESCRIPTION: Jpeg file filter ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ #include #include "Lgi.h" #include "GPalette.h" #if HAS_LIBJPEG #define XMD_H #undef FAR // If you don't want to build with JPEG support then set // the define HAS_LIBJPEG to '0' in Lgi.h // // Linux: // sudo apt-get install libjpeg-dev #include "jpeglib.h" #include #include "GLibraryUtils.h" #if LIBJPEG_SHARED #define JPEGLIB d-> const char sLibrary[] = "libjpeg" #if defined(WINDOWS) _MSC_VER_STR #ifdef WIN64 "x64" #else "x32" #endif #ifdef _DEBUG "d" #endif #endif ; #else #define JPEGLIB #endif // JPEG #if LIBJPEG_SHARED class LibJpeg : public GLibrary { public: LibJpeg() : GLibrary(sLibrary) { #if 0 // def _DEBUG char File[256]; GetModuleFileNameA(Handle(), File, sizeof(File)); LgiTrace("%s:%i - JPEG: %s\n", _FL, File); #endif } DynFunc1(boolean, jpeg_finish_decompress, j_decompress_ptr, cinfo); DynFunc3(JDIMENSION, jpeg_read_scanlines, j_decompress_ptr, cinfo, JSAMPARRAY, scanlines, JDIMENSION, max_lines); DynFunc1(boolean, jpeg_start_decompress, j_decompress_ptr, cinfo); DynFunc2(int, jpeg_read_header, j_decompress_ptr, cinfo, boolean, require_image); DynFunc2(int, jpeg_stdio_src, j_decompress_ptr, cinfo, FILE*, infile); DynFunc1(int, jpeg_destroy_decompress, j_decompress_ptr, cinfo); DynFunc1(int, jpeg_finish_compress, j_compress_ptr, cinfo); DynFunc1(int, jpeg_destroy_compress, j_compress_ptr, cinfo); DynFunc3(JDIMENSION, jpeg_write_scanlines, j_compress_ptr, cinfo, JSAMPARRAY, scanlines, JDIMENSION, num_lines); DynFunc2(int, jpeg_start_compress, j_compress_ptr, cinfo, boolean, write_all_tables); DynFunc1(int, jpeg_set_defaults, j_compress_ptr, cinfo); DynFunc1(struct jpeg_error_mgr *, jpeg_std_error, struct jpeg_error_mgr *, err); DynFunc2(int, jpeg_stdio_dest, j_compress_ptr, cinfo, FILE *, outfile); DynFunc3(int, jpeg_CreateCompress, j_compress_ptr, cinfo, int, version, size_t, structsize); DynFunc3(int, jpeg_CreateDecompress, j_decompress_ptr, cinfo, int, version, size_t, structsize); DynFunc3(int, jpeg_set_quality, j_compress_ptr, cinfo, int, quality, boolean, force_baseline); DynFunc3(int, jpeg_save_markers, j_decompress_ptr, cinfo, int, marker_code, unsigned int, length_limit); }; LMutex JpegLibraryLock("JpegLibraryLock"); GAutoPtr JpegLibrary; #endif #include #include #include "GLibraryUtils.h" #include "GScrollBar.h" #include "GVariant.h" #include "GJpeg.h" #define IJG_JPEG_ICC_MARKER JPEG_APP0 + 2 #define IJG_JPEG_APP_ICC_PROFILE_LEN 0xFFFF #define IJG_MAXIMUM_SEQUENCE_NUMBER 255 #define IJG_SEQUENCE_NUMBER_INDEX 12 #define IJG_NUMBER_OF_MARKERS_INDEX 13 #define IJG_JFIF_ICC_HEADER_LENGTH 14 ///////////////////////////////////////////////////////////////////// class GdcJpegFactory : public GFilterFactory { bool CheckFile(const char *File, int Access, const uchar *Hint) { if (Hint) { if (Hint[6] == 'J' && Hint[7] == 'F' && Hint[8] == 'I' && Hint[9] == 'F') return true; if (Hint[0] == 0xff && Hint[1] == 0xd8 && Hint[2] == 0xff && Hint[3] == 0xe1) return true; } return (File) ? stristr(File, ".jpeg") != 0 || stristr(File, ".jpg") != 0 : false; } GFilter *NewObject() { #if LIBJPEG_SHARED if (JpegLibraryLock.Lock(_FL)) { if (!JpegLibrary) JpegLibrary.Reset(new LibJpeg); JpegLibraryLock.Unlock(); } #endif return new GdcJpeg; } } JpegFactory; ///////////////////////////////////////////////////////////////////// struct my_error_mgr { struct jpeg_error_mgr pub; /* "public" fields */ jmp_buf setjmp_buffer; /* for return to caller */ }; typedef struct my_error_mgr *my_error_ptr; METHODDEF(void) my_error_exit (j_common_ptr cinfo) { my_error_ptr myerr = (my_error_ptr) cinfo->err; // (*cinfo->err->output_message)(cinfo); char buf[256]; (*cinfo->err->format_message)(cinfo, buf); longjmp(myerr->setjmp_buffer, 1); } bool IsJfifIccMarker(jpeg_saved_marker_ptr marker) { return marker->marker == IJG_JPEG_ICC_MARKER && marker->data_length >= IJG_JFIF_ICC_HEADER_LENGTH && GETJOCTET(marker->data[0]) == 0x49 && GETJOCTET(marker->data[1]) == 0x43 && GETJOCTET(marker->data[2]) == 0x43 && GETJOCTET(marker->data[3]) == 0x5F && GETJOCTET(marker->data[4]) == 0x50 && GETJOCTET(marker->data[5]) == 0x52 && GETJOCTET(marker->data[6]) == 0x4F && GETJOCTET(marker->data[7]) == 0x46 && GETJOCTET(marker->data[8]) == 0x49 && GETJOCTET(marker->data[9]) == 0x4C && GETJOCTET(marker->data[10]) == 0x45 && GETJOCTET(marker->data[11]) == 0x0; } bool GetIccProfile(j_decompress_ptr cinfo, uchar **icc_data, uint *icc_datalen) { int num_markers = 0; jpeg_saved_marker_ptr markers = cinfo->marker_list; JOCTET *local_icc_data = NULL; int count = 0; uint total_length; bool marker_present[IJG_MAXIMUM_SEQUENCE_NUMBER]; uint data_length[IJG_MAXIMUM_SEQUENCE_NUMBER]; uint marker_sequence[IJG_MAXIMUM_SEQUENCE_NUMBER]; if (icc_data == NULL || icc_datalen == NULL) return false; *icc_data = NULL; *icc_datalen = 0; ZeroObj(marker_present); for (markers = cinfo->marker_list; markers; markers = markers->next) { if (IsJfifIccMarker(markers)) { int sequence_number = 0; if (num_markers == 0) { num_markers = GETJOCTET(markers->data[IJG_NUMBER_OF_MARKERS_INDEX]); } else if (num_markers != GETJOCTET(markers->data[IJG_NUMBER_OF_MARKERS_INDEX])) { return false; } sequence_number = GETJOCTET(markers->data[IJG_SEQUENCE_NUMBER_INDEX]) - 1; if (sequence_number < 0 || sequence_number > num_markers) { return false; } if (marker_present[sequence_number]) return false; marker_present[sequence_number] = true; data_length[sequence_number] = markers->data_length - IJG_JFIF_ICC_HEADER_LENGTH; marker_sequence[sequence_number] = count; count++; } } if (num_markers == 0) return false; total_length = 0; markers = cinfo->marker_list; for (count = 0; count < num_markers; count++) { int previous_length = total_length; int index = 0; int i = 0; JOCTET *src_pointer; JOCTET *dst_pointer; index = marker_sequence[count]; if (!(marker_present[count])) continue; total_length += data_length[count]; if (local_icc_data != NULL) break; local_icc_data = (JOCTET *)malloc(total_length); if (local_icc_data == NULL) return false; for (i = 0; i < index; i++) { markers = markers->next; if (!markers) { free(local_icc_data); return false; } } dst_pointer = local_icc_data + previous_length; src_pointer = markers->data + IJG_JFIF_ICC_HEADER_LENGTH; while (data_length[count]--) { *dst_pointer++ = *src_pointer++; } } *icc_data = local_icc_data; *icc_datalen = total_length; return true; } struct JpegStream { GStream *f; GArray Buf; }; boolean j_fill_input_buffer(j_decompress_ptr cinfo) { JpegStream *s = (JpegStream*)cinfo->client_data; cinfo->src->next_input_byte = &s->Buf[0]; cinfo->src->bytes_in_buffer = s->f->Read((void*)cinfo->src->next_input_byte, s->Buf.Length()); return cinfo->src->bytes_in_buffer > 0; } void j_init_source(j_decompress_ptr cinfo) { JpegStream *s = (JpegStream*)cinfo->client_data; s->Buf.Length(4 << 10); j_fill_input_buffer(cinfo); } void j_skip_input_data(j_decompress_ptr cinfo, long num_bytes) { // JpegStream *s = (JpegStream*)cinfo->client_data; while (num_bytes) { int Remain = MIN(cinfo->src->bytes_in_buffer, (size_t)num_bytes); if (!Remain) break; cinfo->src->next_input_byte += Remain; cinfo->src->bytes_in_buffer -= Remain; num_bytes -= Remain; if (num_bytes) { j_fill_input_buffer(cinfo); } } } boolean j_resync_to_restart(j_decompress_ptr cinfo, int desired) { // JpegStream *s = (JpegStream*)cinfo->client_data; LgiAssert(0); // not impl return false; } void j_term_source(j_decompress_ptr cinfo) { JpegStream *s = (JpegStream*)cinfo->client_data; s->Buf.Length(0); } GdcJpeg::GdcJpeg() { #if LIBJPEG_SHARED d = JpegLibrary; #endif } typedef GRgb24 JpegRgb; typedef GCmyk32 JpegCmyk; struct JpegXyz { - uint8 x, y, z; + uint8_t x, y, z; }; template void Convert16(T *d, int width) { JpegRgb *s = (JpegRgb*) d; JpegRgb *e = s + width; while (s < e) { JpegRgb t = *s++; d->r = t.r >> 3; d->g = t.g >> 2; d->b = t.b >> 3; d++; } } template void Convert24(T *d, int width) { JpegRgb *e = (JpegRgb*) d; JpegRgb *s = e; d += width - 1; s += width - 1; while (s >= e) { JpegRgb t = *s--; d->r = t.r; d->g = t.g; d->b = t.b; d--; } } template void Convert32(T *d, int width) { JpegRgb *e = (JpegRgb*) d; JpegRgb *s = e; d += width - 1; s += width - 1; while (s >= e) { JpegRgb t = *s--; d->r = t.r; d->g = t.g; d->b = t.b; d->a = 255; d--; } } #define Rc 0 #define Gc (MaxRGB+1) #define Bc (MaxRGB+1)*2 #define DownShift(x) ((int) ((x)+(1L << 15)) >> 16) #define UpShifted(x) ((int) ((x)*(1L << 16)+0.5)) #define MaxRGB 255 template void Ycc24( T *d, int width, long *red, long *green, long *blue, uchar *range_table) { JpegXyz *s = (JpegXyz*) d; T *e = d; uchar *range_limit = range_table + (MaxRGB + 1); d += width - 1; s += width - 1; while (d >= e) { /* Y = 0.299 * R + 0.587 * G + 0.114 * B Cb = -0.169 * R - 0.331 * G + 0.500 * B Cr = 0.500 * R - 0.419 * G - 0.081 * B */ int x = s->x; int y = s->y; int z = s->z; d->r = range_limit[DownShift(red[x+Rc] + green[y+Rc] + blue[z+Rc])]; d->g = range_limit[DownShift(red[x+Gc] + green[y+Gc] + blue[z+Gc])]; d->b = range_limit[DownShift(red[x+Bc] + green[y+Bc] + blue[z+Bc])]; d--; s--; } } template void Ycc32( T *d, int width, long *red, long *green, long *blue, uchar *range_table) { JpegXyz *s = (JpegXyz*) d; T *e = d; uchar *range_limit = range_table + (MaxRGB + 1); d += width - 1; s += width - 1; while (d >= e) { /* Y = 0.299 * R + 0.587 * G + 0.114 * B Cb = -0.169 * R - 0.331 * G + 0.500 * B Cr = 0.500 * R - 0.419 * G - 0.081 * B */ int x = s->x; int y = s->y; int z = s->z; d->r = range_limit[DownShift(red[x+Rc] + green[y+Rc] + blue[z+Rc])]; d->g = range_limit[DownShift(red[x+Gc] + green[y+Gc] + blue[z+Gc])]; d->b = range_limit[DownShift(red[x+Bc] + green[y+Bc] + blue[z+Bc])]; d->a = 255; d--; s--; } } template void CmykToRgb24(D *d, S *s, int width) { D *end = d + width; while (d < end) { int k = s->k; d->r = s->c * k / 255; d->g = s->m * k / 255; d->b = s->y * k / 255; d++; s++; } } template void CmykToRgb32(D *d, S *s, int width) { D *end = d + width; while (d < end) { int k = s->k; int c = s->c * k; int m = s->m * k; int y = s->y * k; d->r = c / 255; d->g = m / 255; d->b = y / 255; d->a = 255; d++; s++; } } GFilter::IoStatus GdcJpeg::ReadImage(GSurface *pDC, GStream *In) { GFilter::IoStatus Status = IoError; GVariant v; #if LIBJPEG_SHARED if (!d->IsLoaded() && !d->Load(sLibrary)) { if (Props) { GString s; s.Printf("libjpeg is missing (%s.%s)", sLibrary, LGI_LIBRARY_EXT); Props->SetValue(LGI_FILTER_ERROR, v = s); } static bool Warn = true; if (Warn) { LgiTrace("%s:%i - Unable to load libjpg (%s.%s).\n", _FL, sLibrary, LGI_LIBRARY_EXT); Warn = false; } return GFilter::IoComponentMissing; } #endif if (!pDC || !In) { return Status; } int row_stride; struct jpeg_decompress_struct cinfo; struct my_error_mgr jerr; JpegStream s; s.f = In; ZeroObj(cinfo); cinfo.err = JPEGLIB jpeg_std_error(&jerr.pub); jerr.pub.error_exit = my_error_exit; cinfo.client_data = &s; if (setjmp(jerr.setjmp_buffer)) { const char *msg = cinfo.err->jpeg_message_table[cinfo.err->msg_code]; if (Props) { Props->SetValue(LGI_FILTER_ERROR, v = msg); } JPEGLIB jpeg_destroy_decompress(&cinfo); return Status; } JPEGLIB jpeg_create_decompress(&cinfo); jpeg_source_mgr Source; cinfo.mem->max_memory_to_use = 512 << 20; ZeroObj(Source); cinfo.src = &Source; cinfo.src->init_source = j_init_source; cinfo.src->fill_input_buffer = j_fill_input_buffer; cinfo.src->skip_input_data = j_skip_input_data; cinfo.src->resync_to_restart = j_resync_to_restart; cinfo.src->term_source = j_term_source; JPEGLIB jpeg_save_markers ( &cinfo, IJG_JPEG_ICC_MARKER, IJG_JPEG_APP_ICC_PROFILE_LEN ); int result = JPEGLIB jpeg_read_header(&cinfo, true); if (result != JPEG_HEADER_OK) { return IoError; } uchar *icc_data = 0; uint icc_len = 0; if (Props && GetIccProfile(&cinfo, &icc_data, &icc_len)) { v.SetBinary(icc_len, icc_data); Props->SetValue(LGI_FILTER_COLOUR_PROF, v); free(icc_data); icc_data = 0; } if (Props) { GStringPipe s(256); s.Print("Sub-sampling: "); for (int i=0; iSetValue(LGI_FILTER_INFO, v = ss.Get()); } int Bits = cinfo.num_components * 8; if (pDC->Create(cinfo.image_width, cinfo.image_height, GBitsToColourSpace(Bits))) { // zero out bitmap pDC->Colour(0, Bits); pDC->Rectangle(); // Progress if (Meter) { Meter->SetDescription("scanlines"); Meter->SetLimits(0, cinfo.image_height-1); } // Read if (cinfo.num_components == 1) { // greyscale GPalette *Pal = new GPalette(0, 256); if (Pal) { GdcRGB *p = (*Pal)[0]; if (p) { for (int i=0; i<256; i++, p++) { p->r = i; p->g = i; p->b = i; } } pDC->Palette(Pal); } } // setup decompress JPEGLIB jpeg_start_decompress(&cinfo); row_stride = cinfo.output_width * cinfo.output_components; long *red = new long[3*(MaxRGB+1)]; long *green = new long[3*(MaxRGB+1)]; long *blue = new long[3*(MaxRGB+1)]; uchar *range_table = new uchar[3*(MaxRGB+1)]; if (red && green && blue && range_table) { int i; for (i=0; i<=MaxRGB; i++) { range_table[i]=0; range_table[i+(MaxRGB+1)]=(uchar) i; range_table[i+(MaxRGB+1)*2]=MaxRGB; } // uchar *range_limit=range_table+(MaxRGB+1); for (i=0; i<=MaxRGB; i++) { red[i+Rc] = UpShifted(1.00000)*i; green[i+Rc] = 0; blue[i+Rc] = UpShifted(1.40200*0.5) * ((i << 1)-MaxRGB); red[i+Gc] = UpShifted(1.00000)*i; green[i+Gc] = (-UpShifted(0.34414*0.5)) * ((i << 1)-MaxRGB); blue[i+Gc] = (-UpShifted(0.71414*0.5)) * ((i << 1)-MaxRGB); red[i+Bc] = UpShifted(1.00000)*i; green[i+Bc] = UpShifted(1.77200*0.5) * ((i << 1)-MaxRGB); blue[i+Bc] = 0; } // loop through scanlines Status = IoSuccess; while ( cinfo.output_scanline < cinfo.output_height && Status == IoSuccess) { uchar *Ptr = (*pDC)[cinfo.output_scanline]; if (Ptr) { if (JPEGLIB jpeg_read_scanlines(&cinfo, &Ptr, 1)) { switch ((int) cinfo.jpeg_color_space) { default: break; case JCS_GRAYSCALE: { // do nothing break; } case JCS_CMYK: { if (cinfo.num_components != 4) { LgiAssert(!"Weird number of components for CMYK JPEG."); break; } LgiAssert(pDC->GetBits() == 32); switch (pDC->GetColourSpace()) { #define CmykCase(name, bits) \ case Cs##name: CmykToRgb##bits((G##name*)Ptr, (GCmyk32*)Ptr, pDC->X()); break CmykCase(Rgb24, 24); CmykCase(Bgr24, 24); CmykCase(Rgbx32, 24); CmykCase(Bgrx32, 24); CmykCase(Xrgb32, 24); CmykCase(Xbgr32, 24); CmykCase(Rgba32, 32); CmykCase(Bgra32, 32); CmykCase(Argb32, 32); CmykCase(Abgr32, 32); #undef CmykCase default: LgiAssert(!"impl me."); Status = IoUnsupportedFormat; break; } break; } case JCS_YCCK: // YCbCrK case 10000: // YCbCr { if (cinfo.num_components == 3) { switch (pDC->GetColourSpace()) { #define YccCase(name, bits) \ case Cs##name: Ycc##bits((G##name*)Ptr, pDC->X(), red, green, blue, range_table); break; YccCase(Rgb24, 24); YccCase(Bgr24, 24); YccCase(Xrgb32, 24); YccCase(Xbgr32, 24); YccCase(Rgbx32, 24); YccCase(Bgrx32, 24); YccCase(Argb32, 32); YccCase(Abgr32, 32); YccCase(Rgba32, 32); YccCase(Bgra32, 32); default: LgiAssert(!"Unsupported colour space."); Status = IoUnsupportedFormat; break; } } break; } case JCS_RGB: case JCS_YCbCr: { if (cinfo.num_components == 3) { int Width = pDC->X(); switch (pDC->GetColourSpace()) { #define JpegCase(name, bits) \ case Cs##name: Convert##bits((G##name*)Ptr, Width); break; JpegCase(Rgb16, 16); JpegCase(Bgr16, 16); JpegCase(Rgb24, 24); JpegCase(Bgr24, 24); JpegCase(Xrgb32, 24); JpegCase(Xbgr32, 24); JpegCase(Rgbx32, 24); JpegCase(Bgrx32, 24); JpegCase(Argb32, 32); JpegCase(Abgr32, 32); JpegCase(Rgba32, 32); JpegCase(Bgra32, 32); default: LgiAssert(!"Unsupported colour space."); Status = IoUnsupportedFormat; break; } } break; } } } else break; } if (Meter) { Meter->Value(cinfo.output_scanline); if (Meter->IsCancelled()) Status = IoCancel; } } } DeleteArray(red); DeleteArray(green); DeleteArray(blue); DeleteArray(range_table); JPEGLIB jpeg_finish_decompress(&cinfo); } JPEGLIB jpeg_destroy_decompress(&cinfo); return Status; } void j_init_destination(j_compress_ptr cinfo) { JpegStream *Stream = (JpegStream*)cinfo->client_data; Stream->Buf.Length(4 << 10); cinfo->dest->free_in_buffer = Stream->Buf.Length(); cinfo->dest->next_output_byte = &Stream->Buf[0]; } boolean j_empty_output_buffer(j_compress_ptr cinfo) { JpegStream *Stream = (JpegStream*)cinfo->client_data; Stream->f->Write(&Stream->Buf[0], Stream->Buf.Length()); cinfo->dest->next_output_byte = &Stream->Buf[0]; cinfo->dest->free_in_buffer = Stream->Buf.Length(); return true; } void j_term_destination(j_compress_ptr cinfo) { JpegStream *Stream = (JpegStream*)cinfo->client_data; int Bytes = Stream->Buf.Length() - cinfo->dest->free_in_buffer; if (Stream->f->Write(&Stream->Buf[0], Bytes) != Bytes) LgiAssert(!"Write failed."); } GFilter::IoStatus GdcJpeg::WriteImage(GStream *Out, GSurface *pDC) { GVariant v; #if LIBJPEG_SHARED if (!d->IsLoaded() && !d->Load(sLibrary)) { if (Props) Props->SetValue(LGI_FILTER_ERROR, v = "libjpeg library isn't installed or wasn't usable."); static bool Warn = true; if (Warn) { LgiTrace("%s:%i - Unabled to load libjpeg.\n", _FL); Warn = false; } return GFilter::IoComponentMissing; } #endif // bool Ok = true; // Setup quality setting GVariant Quality(80), SubSample(Sample_1x1_1x1_1x1), DpiX, DpiY; GdcPt2 Dpi; if (Props) { Props->GetValue(LGI_FILTER_QUALITY, Quality); Props->GetValue(LGI_FILTER_SUBSAMPLE, SubSample); Props->GetValue(LGI_FILTER_DPI_X, DpiX); Props->GetValue(LGI_FILTER_DPI_Y, DpiY); Dpi.x = DpiX.CastInt32(); Dpi.y = DpiY.CastInt32(); } if (!Dpi.x) Dpi.x = 300; if (!Dpi.y) Dpi.y = 300; return _Write(Out, pDC, Quality.CastInt32(), (SubSampleMode)SubSample.CastInt32(), Dpi); } GFilter::IoStatus GdcJpeg::_Write(GStream *Out, GSurface *pDC, int Quality, SubSampleMode SubSample, GdcPt2 Dpi) { struct jpeg_compress_struct cinfo; struct my_error_mgr jerr; int row_stride; cinfo.err = JPEGLIB jpeg_std_error(&jerr.pub); JPEGLIB jpeg_create_compress(&cinfo); jerr.pub.error_exit = my_error_exit; if (setjmp(jerr.setjmp_buffer)) { JPEGLIB jpeg_destroy_compress(&cinfo); return GFilter::IoError; } JpegStream Stream; Stream.f = Out; cinfo.client_data = &Stream; jpeg_destination_mgr Dest; ZeroObj(Dest); Dest.init_destination = j_init_destination; Dest.empty_output_buffer = j_empty_output_buffer; Dest.term_destination = j_term_destination; cinfo.dest = &Dest; bool GreyScale = FALSE; GPalette *Pal = pDC->Palette(); if (pDC->GetBits() == 8) { if (Pal) { GreyScale = true; for (int i=0; iGetSize(); i++) { GdcRGB *p = (*Pal)[i]; if ( p && ( p->r != i || p->g != i || p->b != i ) ) { GreyScale = FALSE; break; } } } else if (!Pal) { GreyScale = true; } } cinfo.image_width = pDC->X(); cinfo.image_height = pDC->Y(); if (GreyScale) { cinfo.input_components = 1; cinfo.in_color_space = JCS_GRAYSCALE; } else { cinfo.input_components = 3; cinfo.in_color_space = JCS_RGB; } JPEGLIB jpeg_set_defaults(&cinfo); switch (SubSample) { default: case Sample_1x1_1x1_1x1: cinfo.comp_info[0].h_samp_factor = 1; cinfo.comp_info[0].v_samp_factor = 1; break; case Sample_2x2_1x1_1x1: cinfo.comp_info[0].h_samp_factor = 2; cinfo.comp_info[0].v_samp_factor = 2; break; case Sample_2x1_1x1_1x1: cinfo.comp_info[0].h_samp_factor = 2; cinfo.comp_info[0].v_samp_factor = 1; break; case Sample_1x2_1x1_1x1: cinfo.comp_info[0].h_samp_factor = 1; cinfo.comp_info[0].v_samp_factor = 2; break; } cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; if (Quality >= 0) { JPEGLIB jpeg_set_quality(&cinfo, Quality, true); } cinfo.X_density = Dpi.x; cinfo.Y_density = Dpi.y; cinfo.density_unit = Dpi.x && Dpi.y ? 1 : 0; JPEGLIB jpeg_start_compress(&cinfo, true); row_stride = pDC->X() * cinfo.input_components; uchar *Buffer = new uchar[row_stride]; if (Buffer) { // Progress if (Meter) { Meter->SetDescription("scanlines"); Meter->SetLimits(0, cinfo.image_height-1); } // Write while (cinfo.next_scanline < cinfo.image_height) { uchar *dst = Buffer; switch (pDC->GetColourSpace()) { case CsIndex8: { if (GreyScale) { memcpy(Buffer, (*pDC)[cinfo.next_scanline], pDC->X()); } else if (Pal) { GdcRGB *p = (*Pal)[0]; if (p) { - uint8 *c = (*pDC)[cinfo.next_scanline]; - uint8 *end = c + pDC->X(); + uint8_t *c = (*pDC)[cinfo.next_scanline]; + uint8_t *end = c + pDC->X(); while (c < end) { GdcRGB &rgb = p[*c++]; dst[0] = rgb.r; dst[1] = rgb.g; dst[2] = rgb.b; dst += 3; } } } break; } case CsRgb16: { GRgb16 *p = (GRgb16*)(*pDC)[cinfo.next_scanline]; GRgb16 *end = p + pDC->X(); while (p < end) { dst[0] = G5bitTo8bit(p->r); dst[1] = G6bitTo8bit(p->g); dst[2] = G5bitTo8bit(p->b); dst += 3; p++; } break; } case CsBgr16: { GBgr16 *p = (GBgr16*)(*pDC)[cinfo.next_scanline]; GBgr16 *end = p + pDC->X(); while (p < end) { dst[0] = (p->r << 3) | (p->r >> 5); dst[1] = (p->g << 2) | (p->g >> 6); dst[2] = (p->b << 3) | (p->b >> 5); dst += 3; p++; } break; } case System24BitColourSpace: { System24BitPixel *p, *end; p = (System24BitPixel*) (*pDC)[cinfo.next_scanline]; end = p + pDC->X(); while (p < end) { *dst++ = p->r; *dst++ = p->g; *dst++ = p->b; p++; } break; } case System32BitColourSpace: { System32BitPixel *p = (System32BitPixel*) (*pDC)[cinfo.next_scanline]; System32BitPixel *end = p + pDC->X(); while (p < end) { *dst++ = p->r; *dst++ = p->g; *dst++ = p->b; p++; } break; } default: LgiAssert(0); break; } JPEGLIB jpeg_write_scanlines(&cinfo, &Buffer, 1); if (Meter) Meter->Value(cinfo.next_scanline); } DeleteArray(Buffer); } JPEGLIB jpeg_finish_compress(&cinfo); JPEGLIB jpeg_destroy_compress(&cinfo); return GFilter::IoSuccess; } #endif diff --git a/src/common/Gdc2/Filters/Libtiff.cpp b/src/common/Gdc2/Filters/Libtiff.cpp --- a/src/common/Gdc2/Filters/Libtiff.cpp +++ b/src/common/Gdc2/Filters/Libtiff.cpp @@ -1,908 +1,908 @@ /*hdr ** FILE: Tiff.cpp ** AUTHOR: Matthew Allen ** DATE: 9/10/98 ** DESCRIPTION: Tiff filter ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ #include #ifdef WIN32 #include #endif #include #include #include "Lgi.h" #include "Lzw.h" #include "GVariant.h" #include "GLibraryUtils.h" #include "GPalette.h" namespace t { #include "libtiff/tiffio.h" } // Libtiff support class LibTiff : public GLibrary { public: LibTiff() : GLibrary ( "libtiff" #if defined(_WIN64) "9x64" #elif defined(WIN32) "9x32" #endif #if 0 // defined(_DEBUG) && defined(WIN32) "d" #endif ) {} DynFunc2(t::TIFF*, TIFFOpen, const char*, a, const char*, b); DynFunc10(t::TIFF*, TIFFClientOpen, const char*, a, const char*, b, t::thandle_t, c, t::TIFFReadWriteProc, d, t::TIFFReadWriteProc, e, t::TIFFSeekProc, f, t::TIFFCloseProc, g, t::TIFFSizeProc, h, t::TIFFMapFileProc, i, t::TIFFUnmapFileProc, j); DynFunc1(int, TIFFClose, t::TIFF*, t); DynFunc1(t::TIFFErrorHandler, TIFFSetErrorHandler, t::TIFFErrorHandler, e); DynFunc4(int, TIFFRGBAImageBegin, t::TIFFRGBAImage*, a, t::TIFF*, b, int, c, char*, d); DynFunc1(int, TIFFRGBAImageEnd, t::TIFFRGBAImage*, a); - DynFunc4(int, TIFFRGBAImageGet, t::TIFFRGBAImage*, img, uint32*, ptr, uint32, x, uint32, y); - DynFunc4(int, TIFFWriteScanline, t::TIFF*, tif, t::tdata_t, ptr, uint32, a, t::tsample_t, b); - DynFunc4(int, TIFFReadScanline, t::TIFF*, tif, t::tdata_t, ptr, uint32, a, t::tsample_t, b); + DynFunc4(int, TIFFRGBAImageGet, t::TIFFRGBAImage*, img, uint32_t*, ptr, uint32_t, x, uint32_t, y); + DynFunc4(int, TIFFWriteScanline, t::TIFF*, tif, t::tdata_t, ptr, uint32_t, a, t::tsample_t, b); + DynFunc4(int, TIFFReadScanline, t::TIFF*, tif, t::tdata_t, ptr, uint32_t, a, t::tsample_t, b); DynFunc1(int, TIFFScanlineSize, t::TIFF*, t); int TIFFGetField(t::TIFF *tif, t::ttag_t tag, ...) { va_list Args; va_start(Args, tag); typedef int (*pTIFFVGetField)(t::TIFF*, t::ttag_t, va_list); pTIFFVGetField TIFFVGetField = (pTIFFVGetField) GetAddress("TIFFVGetField"); if (TIFFVGetField) return TIFFVGetField(tif, tag, Args); return 0; } int TIFFSetField(t::TIFF *tif, t::ttag_t tag, ...) { va_list Args; va_start(Args, tag); typedef int (*pTIFFVSetField)(t::TIFF*, t::ttag_t, va_list); pTIFFVSetField TIFFVSetField = (pTIFFVSetField) GetAddress("TIFFVSetField"); if (TIFFVSetField) return TIFFVSetField(tif, tag, Args); return 0; } }; class GdcLibTiff : public GFilter { GAutoPtr Lib; public: GdcLibTiff(); ~GdcLibTiff(); int GetCapabilites() { return FILTER_CAP_READ|FILTER_CAP_WRITE; } Format GetFormat() { return FmtTiff; } bool IsOk() { return Lib ? Lib->IsLoaded() : false; } IoStatus ReadImage(GSurface *pDC, GStream *In); IoStatus WriteImage(GStream *Out, GSurface *pDC); bool GetVariant(const char *n, GVariant &v, char *a) { if (!_stricmp(n, LGI_FILTER_TYPE)) { v = "Tiff"; } else if (!_stricmp(n, LGI_FILTER_EXTENSIONS)) { v = "TIF,TIFF"; } else return false; return true; } }; // Object Factory class GdcTiffFactory : public GFilterFactory { bool CheckFile(const char *File, int Access, const uchar *Hint) { if (Access == FILTER_CAP_READ || Access == FILTER_CAP_WRITE) { return (File) ? stristr(File, ".tiff") != 0 || stristr(File, ".tif") != 0 : false; } return false; } GFilter *NewObject() { return new GdcLibTiff; } } TiffFactory; static t::tsize_t TRead(t::thandle_t Hnd, t::tdata_t Ptr, t::tsize_t Size) { GStream *s = (GStream*)Hnd; int r = s->Read(Ptr, Size); return r; } static t::tsize_t TWrite(t::thandle_t Hnd, t::tdata_t Ptr, t::tsize_t Size) { GStream *s = (GStream*)Hnd; int w = s->Write(Ptr, Size); return w; } static t::toff_t TSeek(t::thandle_t Hnd, t::toff_t Where, int Whence) { GStream *s = (GStream*)Hnd; switch (Whence) { case SEEK_SET: s->SetPos(Where); break; case SEEK_CUR: s->SetPos(s->GetPos() + Where); break; case SEEK_END: s->SetPos(s->GetSize() + Where); break; } return (t::toff_t) s->GetPos(); } static int TClose(t::thandle_t Hnd) { GStream *s = (GStream*)Hnd; return !s->Close(); } static t::toff_t TSize(t::thandle_t Hnd) { GStream *s = (GStream*)Hnd; t::toff_t size = (t::toff_t) s->GetSize(); return size; } static char PrevError[1024] = "(none)"; static void TError(const char *Func, const char *Fmt, va_list Args) { vsprintf_s(PrevError, Fmt, Args); } GdcLibTiff::GdcLibTiff() { Lib.Reset(new LibTiff); if (IsOk()) { Lib->TIFFSetErrorHandler(TError); } } GdcLibTiff::~GdcLibTiff() { } /// This swaps the R and B components as well as flips the image on the Y /// axis. Which is the required format to read and write TIFF's. Use it a /// 2nd time to restore the image to it's original state. void SwapRBandY(GSurface *pDC) { for (int y1=0, y2 = pDC->Y()-1; y1<=y2; y1++, y2--) { switch (pDC->GetColourSpace()) { case System24BitColourSpace: { System24BitPixel *s1 = (System24BitPixel*)(*pDC)[y1]; System24BitPixel *s2 = (System24BitPixel*)(*pDC)[y2]; System24BitPixel *e1 = (System24BitPixel*) ((*pDC)[y1] + (pDC->X() * sizeof(System24BitPixel))); if (y1 == y2) { - uint8 t; + uint8_t t; while (s1 < e1) { t = s1->r; s1->r = s1->b; s1->b = t; s1++; } } else { System24BitPixel t; while (s1 < e1) { t = *s1; s1->r = s2->b; s1->g = s2->g; s1->b = s2->r; s2->r = t.b; s2->g = t.g; s2->b = t.r; s1++; s2++; } } break; } case System32BitColourSpace: { System32BitPixel *s1 = (System32BitPixel*)(*pDC)[y1]; System32BitPixel *s2 = (System32BitPixel*)(*pDC)[y2]; System32BitPixel *e1 = s1 + pDC->X(); if (y1 == y2) { - uint8 t; + uint8_t t; while (s1 < e1) { t = s1->r; s1->r = s1->b; s1->b = t; s1++; } } else { System32BitPixel t; while (s1 < e1) { t = *s1; s1->r = s2->b; s1->g = s2->g; s1->b = s2->r; s1->a = s2->a; s2->r = t.b; s2->g = t.g; s2->b = t.r; s2->a = t.a; s1++; s2++; } } break; } default: { LgiAssert(!"Not impl."); break; } } } } void SwapRB(GSurface *pDC) { for (int y1=0; y1Y(); y1++) { switch (pDC->GetColourSpace()) { case CsIndex8: // No swap needed return; break; case System24BitColourSpace: { System24BitPixel *s1 = (System24BitPixel*)(*pDC)[y1]; System24BitPixel *e1 = (System24BitPixel*) ((*pDC)[y1] + (pDC->X() * sizeof(System24BitPixel))); - uint8 t; + uint8_t t; while (s1 < e1) { t = s1->r; s1->r = s1->b; s1->b = t; s1++; } break; } case System32BitColourSpace: { System32BitPixel *s1 = (System32BitPixel*)(*pDC)[y1]; System32BitPixel *e1 = s1 + pDC->X(); - uint8 t; + uint8_t t; while (s1 < e1) { t = s1->r; s1->r = s1->b; s1->b = t; s1++; } break; } default: { LgiAssert(!"Not impl."); break; } } } } struct Cmyka { - uint8 c, m, y, k, a; + uint8_t c, m, y, k, a; }; template void TiffProcess24(D *d, S *s, int width) { D *e = d + width; while (d < e) { d->r = s->r; d->g = s->g; d->b = s->b; d++; s++; } } template void TiffProcess24To32(D *d, S *s, int width) { D *e = d + width; while (d < e) { d->r = s->r; d->g = s->g; d->b = s->b; d->a = 255; d++; s++; } } template void TiffProcess32(D *d, S *s, int width) { D *e = d + width; while (d < e) { d->r = s->r; d->g = s->g; d->b = s->b; d->a = s->a; d++; s++; } } template void CmykToRgb24(D *d, S *s, int width) { D *end = d + width; while (d < end) { double C = (double) s->c / 255.0; double M = (double) s->m / 255.0; double Y = (double) s->y / 255.0; double K = (double) s->k / 255.0; d->r = (int) ((1.0 - min(1, C * (1.0 - K) + K)) * 255.0); d->g = (int) ((1.0 - min(1, M * (1.0 - K) + K)) * 255.0); d->b = (int) ((1.0 - min(1, Y * (1.0 - K) + K)) * 255.0); d++; s++; } } template void CmykToRgb32(D *d, S *s, int width) { D *end = d + width; while (d < end) { double C = (double) s->c / 255.0; double M = (double) s->m / 255.0; double Y = (double) s->y / 255.0; double K = (double) s->k / 255.0; d->r = (int) ((1.0 - min(1, C * (1.0 - K) + K)) * 255.0); d->g = (int) ((1.0 - min(1, M * (1.0 - K) + K)) * 255.0); d->b = (int) ((1.0 - min(1, Y * (1.0 - K) + K)) * 255.0); d->a = 255; d++; s++; } } GFilter::IoStatus GdcLibTiff::ReadImage(GSurface *pDC, GStream *In) { GVariant v; if (!pDC || !In) { Props->SetValue(LGI_FILTER_ERROR, v = "Parameter error."); return IoError; } if (!IsOk()) { Props->SetValue(LGI_FILTER_ERROR, v = "Can't find libtiff library."); return IoComponentMissing; } IoStatus Status = IoError; t::TIFF *tif = Lib->TIFFClientOpen("", "rm", In, TRead, TWrite, TSeek, TClose, TSize, 0, 0); if (tif) { int Photometric = 0; int Inkset = 0; int Samples = 0; int BitsPerSample = 0; int Planar = 0; Lib->TIFFGetField(tif, TIFFTAG_PLANARCONFIG, &Planar); Lib->TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &BitsPerSample); Lib->TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &Samples); Lib->TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &Photometric); Lib->TIFFGetField(tif, TIFFTAG_INKSET, &Inkset); t::TIFFRGBAImage img; if (Lib->TIFFRGBAImageBegin(&img, tif, 0, PrevError)) { int Bits = img.bitspersample * img.samplesperpixel; if (img.samplesperpixel == 5) { int rowlen = img.width * img.samplesperpixel * img.bitspersample / 8; - GArray a; + GArray a; if (a.Length(rowlen) && pDC->Create(img.width, img.height, System32BitColourSpace)) { if (Meter) Meter->SetLimits(0, img.height); for (unsigned y=0; yTIFFReadScanline(tif, &a[0], y, 0); if (r != 1) break; System32BitPixel *d = (System32BitPixel*) (*pDC)[y]; System32BitPixel *e = d + img.width; Cmyka *s = (Cmyka*) &a[0]; while (d < e) { double C = (double) s->c / 255; double M = (double) s->m / 255; double Y = (double) s->y / 255; double K = (double) s->k / 255; d->r = (int) ((1.0 - min(1, C * (1.0 - K) + K)) * 255); d->g = (int) ((1.0 - min(1, M * (1.0 - K) + K)) * 255); d->b = (int) ((1.0 - min(1, Y * (1.0 - K) + K)) * 255); d->a = s->a; d++; s++; } if (Meter) Meter->Value(y); } Status = IoSuccess; } } else { if (pDC->Create(img.width, img.height, GBitsToColourSpace(max(Bits, 8)))) { if (Meter) Meter->SetLimits(0, img.height); switch (Bits) { default: { LgiAssert(!"impl me."); break; } case 1: { - GArray Buf; + GArray Buf; Buf.Length(img.width + 7 / 8); for (unsigned y=0; yTIFFReadScanline(tif, (t::tdata_t)Ptr, y, 0); // Unpack bits into bytes.. int Mask = 0x80; for (int i=0; i>= 1; if (!Mask) { Mask = 0x80; Ptr++; } } if (Meter && (y % 32) == 0) Meter->Value(y); } break; } case 8: { // Read in the palette uint16 *rmap = NULL, *gmap = NULL, *bmap = NULL; int Result = Lib->TIFFGetField(tif, TIFFTAG_COLORMAP, &rmap, &gmap, &bmap); if (Result) { GPalette *p = new GPalette; if (p) { p->SetSize(256); for (int i=0; i<256; i++) { GdcRGB *c = (*p)[i]; if (c) { c->r = rmap[i] >> 8; c->g = gmap[i] >> 8; c->b = bmap[i] >> 8; } } pDC->Palette(p); } } // Read in the pixels for (unsigned y=0; yTIFFReadScanline(tif, (t::tdata_t)d, y, 0); if (Meter && (y % 32) == 0) Meter->Value(y); } break; } case 24: { GArray Buf; Buf.Length(img.width); GRgb24 *b = &Buf[0]; LgiAssert(Lib->TIFFScanlineSize(tif) == Buf.Length() * sizeof(Buf[0])); LgiAssert(Photometric == PHOTOMETRIC_RGB); // we don't support anything else yet. for (unsigned y=0; yTIFFReadScanline(tif, (t::tdata_t)b, y, 0); switch (pDC->GetColourSpace()) { #define TiffCase(name, bits) \ case Cs##name: TiffProcess##bits((G##name*)d, b, pDC->X()); break TiffCase(Rgb24, 24); TiffCase(Bgr24, 24); TiffCase(Xrgb32, 24); TiffCase(Rgbx32, 24); TiffCase(Xbgr32, 24); TiffCase(Bgrx32, 24); TiffCase(Rgba32, 24To32); TiffCase(Bgra32, 24To32); TiffCase(Argb32, 24To32); TiffCase(Abgr32, 24To32); #undef TiffCase default: LgiAssert(!"impl me."); break; } if (Meter && (y % 32) == 0) Meter->Value(y); } break; } case 32: { - GArray Buf; + GArray Buf; Buf.Length(img.width); - uint32 *b = &Buf[0]; + uint32_t *b = &Buf[0]; LgiAssert(Lib->TIFFScanlineSize(tif) == Buf.Length() * sizeof(Buf[0])); GColourSpace DestCs = pDC->GetColourSpace(); for (unsigned y=0; yTIFFReadScanline(tif, (t::tdata_t)b, y, 0); if (Photometric == PHOTOMETRIC_SEPARATED) { switch (DestCs) { #define TiffCase(name, bits) \ case Cs##name: CmykToRgb##bits((G##name*)d, (GCmyk32*)b, pDC->X()); break TiffCase(Rgb24, 24); TiffCase(Bgr24, 24); TiffCase(Rgbx32, 24); TiffCase(Bgrx32, 24); TiffCase(Xrgb32, 24); TiffCase(Xbgr32, 24); TiffCase(Rgba32, 32); TiffCase(Bgra32, 32); TiffCase(Argb32, 32); TiffCase(Abgr32, 32); #undef TiffCase default: LgiAssert(!"impl me."); break; } } else if (Photometric == PHOTOMETRIC_RGB) { switch (DestCs) { #define TiffCase(name, bits) \ case Cs##name: TiffProcess##bits((G##name*)d, (GRgba32*)b, pDC->X()); break TiffCase(Rgb24, 24); TiffCase(Bgr24, 24); TiffCase(Xrgb32, 24); TiffCase(Rgbx32, 24); TiffCase(Xbgr32, 24); TiffCase(Bgrx32, 24); TiffCase(Rgba32, 32); TiffCase(Bgra32, 32); TiffCase(Argb32, 32); TiffCase(Abgr32, 32); #undef TiffCase default: LgiAssert(!"impl me."); break; } } else LgiAssert(0); if (Meter && (y % 32) == 0) Meter->Value(y); } break; } case 48: { GArray Buf; Buf.Length(img.width); GRgb48 *b = &Buf[0]; LgiAssert(Lib->TIFFScanlineSize(tif) == Buf.Length() * sizeof(Buf[0])); for (unsigned y=0; yTIFFReadScanline(tif, (t::tdata_t)b, y, 0); switch (pDC->GetColourSpace()) { #define TiffCase(name, bits) \ case Cs##name: TiffProcess##bits((G##name*)d, b, pDC->X()); break TiffCase(Rgb48, 24); TiffCase(Bgr48, 24); #undef TiffCase default: LgiAssert(!"impl me."); break; } if (Meter && (y % 32) == 0) Meter->Value(y); } break; } case 64: { GArray Buf; Buf.Length(img.width); GRgba64 *b = &Buf[0]; LgiAssert(Lib->TIFFScanlineSize(tif) == Buf.Length() * sizeof(Buf[0])); for (unsigned y=0; yTIFFReadScanline(tif, (t::tdata_t)b, y, 0); switch (pDC->GetColourSpace()) { #define TiffCase64(name, bits) \ case Cs##name: TiffProcess##bits((G##name*)d, (GRgba32*)b, pDC->X()); break TiffCase64(Rgba64, 32); TiffCase64(Bgra64, 32); #undef TiffCase64 default: LgiAssert(!"impl me."); break; } if (Meter && (y % 32) == 0) Meter->Value(y); } break; } } Status = IoSuccess; } } Lib->TIFFRGBAImageEnd(&img); } else { Props->SetValue(LGI_FILTER_ERROR, v = PrevError); } Lib->TIFFClose(tif); } else { Props->SetValue(LGI_FILTER_ERROR, v = PrevError); } return Status; } GFilter::IoStatus GdcLibTiff::WriteImage(GStream *Out, GSurface *pDC) { GVariant v; if (!pDC || !Out) { Props->SetValue(LGI_FILTER_ERROR, v = "Parameter error."); return IoError; } if (!IsOk()) { Props->SetValue(LGI_FILTER_ERROR, v = "Can't find libtiff library."); return IoUnsupportedFormat; } IoStatus Status = IoError; t::TIFF *tif = Lib->TIFFClientOpen("", "wm", Out, TRead, TWrite, TSeek, TClose, TSize, 0, 0); if (tif) { int bits = pDC->GetBits(); int ComponentSize = bits > 32 ? 16 : 8; Lib->TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, pDC->X()); Lib->TIFFSetField(tif, TIFFTAG_IMAGELENGTH, pDC->Y()); Lib->TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, bits / ComponentSize); Lib->TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, ComponentSize); Lib->TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); Lib->TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); switch (bits) { case 1: { Lib->TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK); break; } case 2: case 3: case 4: case 5: case 6: case 7: case 8: { Lib->TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_PALETTE); GPalette *p = pDC->Palette(); if (p) { uint16 rmap[256], gmap[256], bmap[256]; int colours = 1 << bits; for (int i=0; ir); gmap[i] = G8bitTo16bit(rgb->g); bmap[i] = G8bitTo16bit(rgb->b); } else { rmap[i] = G8bitTo16bit(i); gmap[i] = G8bitTo16bit(i); bmap[i] = G8bitTo16bit(i); } } Lib->TIFFSetField(tif, TIFFTAG_COLORMAP, rmap, gmap, bmap); } break; } default: { Lib->TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); break; } } Status = IoSuccess; SwapRB(pDC); if (Meter) Meter->SetLimits(0, pDC->Y()); for (int y=0; yY(); y++) { - uint8 *Scan = (*pDC)[y]; + uint8_t *Scan = (*pDC)[y]; if (Lib->TIFFWriteScanline(tif, Scan, y, 0) != 1) { Status = IoError; break; } if (Meter && (y % 32) == 0) Meter->Value(y); } Lib->TIFFClose(tif); SwapRB(pDC); } else { Props->SetValue(LGI_FILTER_ERROR, v = PrevError); } return Status; } diff --git a/src/common/Gdc2/Filters/Tiff.cpp b/src/common/Gdc2/Filters/Tiff.cpp --- a/src/common/Gdc2/Filters/Tiff.cpp +++ b/src/common/Gdc2/Filters/Tiff.cpp @@ -1,1080 +1,1080 @@ /*hdr ** FILE: Tiff.cpp ** AUTHOR: Matthew Allen ** DATE: 9/10/98 ** DESCRIPTION: Tiff filter ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ #include #ifdef WIN32 #include #endif #include #include #include "Lgi.h" #include "Lzw.h" #include "GVariant.h" #include "GPalette.h" // TIFF #define TIFF_USE_LZW class TiffIo : public GFilter { public: GStream *s; TiffIo() { s = 0; } Format GetFormat() { return FmtTiff; } bool GetSwap() { return false; } bool Read(void *p, int len) { return GFilter::Read(s, p, len); } bool Write(const void *p, int len) { return GFilter::Write(s, p, len); } }; class IFD { void *Data; public: ushort Tag; ushort Type; ulong Count; ulong Offset; IFD(); ~IFD(); void *GetData(); char *Str(); int ElementSize(); int Value(); int ArrayValue(int i); bool Read(TiffIo &f); bool Write(TiffIo &f); }; class GdcTiff : public TiffIo { List Blocks; IFD *FindTag(ushort n); int ScanLength; #ifdef TIFF_USE_LZW Lzw Lib; #endif IoStatus ProcessRead(GSurface *pDC); IoStatus ProcessWrite(GSurface *pDC); public: GView *Parent; OsView Handle(); GdcTiff(); ~GdcTiff(); int GetCapabilites() { return FILTER_CAP_READ; } IoStatus ReadImage(GSurface *pDC, GStream *In); IoStatus WriteImage(GStream *Out, GSurface *pDC); bool GetVariant(const char *n, GVariant &v, char *a) { if (!stricmp(n, LGI_FILTER_TYPE)) { v = "Tiff"; } else if (!stricmp(n, LGI_FILTER_EXTENSIONS)) { v = "TIF,TIFF"; } else return false; return true; } }; // Object Factory class GdcTiffFactory : public GFilterFactory { bool CheckFile(const char *File, int Access, const uchar *Hint) { if (Access == FILTER_CAP_READ) { return (File) ? stristr(File, ".tiff") != 0 || stristr(File, ".tif") != 0 : false; } return false; } GFilter *NewObject() { return new GdcTiff; } } TiffFactory; // Tiff tag numbers #define TAG_ImageX 0x100 #define TAG_ImageY 0x101 #define TAG_Bits 0x102 #define TAG_Palette 0x140 #define TAG_Compression 0x103 #define TAG_RowsPerStrip 0x116 #define TAG_StripOffsets 0x111 #define TAG_StripByteOffsets 0x117 #define TAG_PhotometricInterpretation 0x106 // PHOTOMETRIC_???? #define TAG_Predictor 0x13D #define PHOTOMETRIC_MINISWHITE 0 #define PHOTOMETRIC_MINISBLACK 1 #define PHOTOMETRIC_RGB 2 #define PHOTOMETRIC_PALETTE 3 #define PHOTOMETRIC_MASK 4 #define PHOTOMETRIC_CMYK 5 #define PHOTOMETRIC_YCBCR 6 #define PHOTOMETRIC_CIELAB 8 #define PHOTOMETRIC_ICCLAB 9 #define PHOTOMETRIC_ITULAB 10 #define PHOTOMETRIC_LOGL 32844 #define PHOTOMETRIC_LOGLUV 32845 // Tiff tag data types #define TYPE_UBYTE 1 #define TYPE_USHORT 3 #define TYPE_ULONG 4 #define TYPE_SBYTE 6 #define TYPE_SSHORT 8 #define TYPE_SLONG 9 #define TYPE_ASCII 2 #define TYPE_URATIONAL 5 #define TYPE_UNDEFINED 7 #define TYPE_SRATIONAL 10 #define TYPE_FLOAT 11 #define TYPE_DOUBLE 12 //////////////////////////////////////////////////////////////////////////////////////////// IFD::IFD() { Data = 0; } IFD::~IFD() { DeleteArray((uchar*&)Data); } void *IFD::GetData() { return (Data) ? Data : &Offset; } int IFD::ElementSize() { switch (Type) { case TYPE_UBYTE: case TYPE_SBYTE: case TYPE_ASCII: case TYPE_UNDEFINED: { return 1; } case TYPE_USHORT: case TYPE_SSHORT: { return 2; } case TYPE_ULONG: case TYPE_SLONG: case TYPE_FLOAT: { return 4; } case TYPE_URATIONAL: case TYPE_SRATIONAL: case TYPE_DOUBLE: { return 8; } } return 0; } char *IFD::Str() { if (Data) { return (char*) Data; } return (char*) &Offset; } int IFD::Value() { if (Count == 1) { switch (Type) { case TYPE_UBYTE: { return (int) ((uchar) (Offset & 0xFF)); } case TYPE_USHORT: { return (int) ((ushort) (Offset & 0xFFFF)); } case TYPE_ULONG: { return (int) ((ulong) Offset); } case TYPE_SBYTE: { return (int) ((signed char) (Offset & 0xFF)); } case TYPE_SSHORT: { return (int) ((short) (Offset & 0xFFFF)); } case TYPE_SLONG: { return (int) ((long) Offset); } case TYPE_ASCII: { char *s = Str(); return (s) ? atoi(s) : 0; } case TYPE_URATIONAL: case TYPE_UNDEFINED: case TYPE_SRATIONAL: case TYPE_FLOAT: case TYPE_DOUBLE: { break; } } } return 0; } int IFD::ArrayValue(int i) { void *p = GetData(); if (i < Count && p) { switch (Type) { case TYPE_ASCII: case TYPE_UBYTE: { return (int) ((uchar*)p)[i]; } case TYPE_USHORT: { return (int) ((ushort*)p)[i]; } case TYPE_ULONG: { return (int) ((ulong*)p)[i]; } case TYPE_SBYTE: { return (int) ((signed char*)p)[i]; } case TYPE_SSHORT: { return (int) ((short*)p)[i]; } case TYPE_SLONG: { return (int) ((long*)p)[i]; } } } else { printf("%s:%i - IFD::ArrayValue(%i) error, Count=%i p=%p\n", _FL, i, (int)Count, p); } return 0; } bool IFD::Read(TiffIo &f) { f.Read(&Tag, sizeof(Tag)); f.Read(&Type, sizeof(Type)); f.Read(&Count, sizeof(Count)); f.Read(&Offset, sizeof(Offset)); int Size = Count * ElementSize(); if (Size > 4) { Data = new uchar[Size]; if (Data) { int Pos = f.s->GetPos(); f.s->SetPos(Offset); switch (Type) { case TYPE_USHORT: { uint16 *p = (uint16*)Data; for (int i=0; iRead(Data, Size); break; } } f.s->SetPos(Pos); } } else if (f.GetSwap()) { switch (Type) { case TYPE_UBYTE: case TYPE_SBYTE: { Offset >>= 24; break; } case TYPE_USHORT: case TYPE_SSHORT: { Offset >>= 16; break; } } } return true; } bool IFD::Write(TiffIo &f) { f.Write(&Tag, sizeof(Tag)); f.Write(&Type, sizeof(Type)); f.Write(&Count, sizeof(Count)); f.Write(&Offset, sizeof(Offset)); return true; } //////////////////////////////////////////////////////////////////////////////////////////// GdcTiff::GdcTiff() { Parent = 0; } GdcTiff::~GdcTiff() { } OsView GdcTiff::Handle() { return (Parent) ? Parent->Handle() : 0; } IFD *GdcTiff::FindTag(ushort n) { for (IFD *i = Blocks.First(); i; i = Blocks.Next()) { if (i->Tag == n) { return i; } } return NULL; } class TiffPipe : public GMemQueue { Progress *p; int Scansize; int Last; int64 Size; public: TiffPipe(Progress *prog, int scansize, int size) : GMemQueue(size) { p = prog; Last = 0; Size = 0; Scansize = scansize; } ssize_t Write(const void *buf, ssize_t size, int flags) { int Status = GMemQueue::Write(buf, size, flags); Size += size; if (p) { int y = Size / Scansize; if (y > Last + 64) { p->Value(y); Last = y; LgiTrace("y=%i\n", y); } } return Status; } }; GFilter::IoStatus GdcTiff::ProcessRead(GSurface *pDC) { GFilter::IoStatus Status = IoError; bool Error = false; IFD *x = FindTag(TAG_ImageX); IFD *y = FindTag(TAG_ImageY); IFD *BitTag = FindTag(TAG_Bits); int Bits = 1; int Pos = s->GetPos(); if (BitTag) { switch (BitTag->Count) { case 1: { Bits = BitTag->Value(); break; } case 3: { ushort *Depth = (ushort*) BitTag->GetData(); if (Depth && BitTag->Type == TYPE_USHORT) { // Baseline RGB image Bits = Depth[0] + Depth[1] + Depth[2]; } break; } case 4: { // Maybe a 32-bit image? ushort *Depth = (ushort*) BitTag->GetData(); if (Depth && BitTag->Type == TYPE_USHORT) { // CMYK image? Bits = Depth[0] + Depth[1] + Depth[2] + Depth[3]; } break; } } } int X = x ? x->Value() : 0; int Y = y ? y->Value() : 0; int B = 0; bool DownSam = false; if (Bits > 32) { B = 32; DownSam = true; } else { B = Bits >= 8 ? Bits : 8; } #ifdef MAC if (B == 24) B = 32; #endif if (pDC && pDC->Create(X, Y, CsIndex8)) { if (Meter) { // Set the meter block to the size of a scanline Lib.MeterBlock = (X * Bits) >> (DownSam ? 2 : 3); Lib.Meter = Meter; Meter->SetDescription("scanlines"); Meter->SetLimits(0, Y-1); } pDC->Colour(0); pDC->Rectangle(); // Read Palette IFD *Palette = FindTag(TAG_Palette); if (Palette) { ushort *s = (ushort*) Palette->GetData(); if (s && Palette->Type == TYPE_USHORT) { int Colours = Palette->Count / 3; GPalette *Pal = new GPalette(0, Colours); if (Pal) { for (int i=0; ir = s[(0*Colours)+i] >> 8; d->g = s[(1*Colours)+i] >> 8; d->b = s[(2*Colours)+i] >> 8; } } pDC->Palette(Pal); } } } // Read Image IFD *Compression = FindTag(TAG_Compression); IFD *RowsPerStrip = FindTag(TAG_RowsPerStrip); IFD *StripOffsets = FindTag(TAG_StripOffsets); IFD *StripByteCounts = FindTag(TAG_StripByteOffsets); IFD *PhotometricInterpretation = FindTag(TAG_PhotometricInterpretation); if (Compression && StripOffsets && StripByteCounts) { int Comp = Compression->Value(); int Rows = RowsPerStrip ? RowsPerStrip->Value() : Y; int Interpretation = PhotometricInterpretation->Value(); int Cy = 0; switch (Bits) { case 1: case 8: case 24: case 32: case 64: { int Strip = 0; ScanLength = ((pDC->X() * Bits) + 7) / 8; for ( ; !Error && CyY() && Strip < StripOffsets->Count; Strip++) { int Offset = StripOffsets->ArrayValue(Strip); if (Offset) { Lib.SetBufSize(Offset); } else { Error = true; break; } int Limit = MIN(pDC->Y() - Cy, Rows); s->SetPos(Offset); switch (Comp) { case 1: // uncompressed { for (int i=0; iRead(d, ScanLength); } } break; } case 32773: // Pack-bits RLE { for (int i=0; iRead(d, n); } d += n; } } } break; } case 2: // modified huffman { if (Props) { GVariant v; Props->SetValue(LGI_FILTER_ERROR, v = "This image uses a modified huffman compression,\n" "Which is not supported yet.\n"); } Error = true; break; } case 5: // LZW compression { #ifdef TIFF_USE_LZW TiffPipe Out(Meter, ScanLength, 512 << 10); if (Lib.Decompress(&Out, s)) { // int Bytes = Out.GetSize(); uchar *FullRez = (DownSam) ? new uchar[ScanLength] : 0; for (; Out.GetSize() >= ScanLength; Cy++) { uchar *o = (*pDC)[Cy]; if (o) { if (DownSam) { if (FullRez) { Out.Read(FullRez, ScanLength); uint16 *s = (uint16*) FullRez; uint16 *e = s + (pDC->X() * 4); uchar *d = o; while (s < e) { *d++ = *s++ >> 8; } } } else { Out.Read(o, ScanLength); } } } DeleteArray(FullRez); } else { printf("%s:%i - LZW decompress failed.\n", __FILE__, __LINE__); } #else Props.Set(OptErrMsg, "This image uses LZW, which is unsupported due to\r\n" "UniSys requiring a royalty for it's patented algorithm.\r\n" "\r\n" "Use PNG instead."); Error = true; #endif break; } default: { if (Props) { char Msg[256]; sprintf(Msg, "This image uses an unsupported TIFF compression method: %i", Comp); GVariant v = Msg; Props->SetValue(LGI_FILTER_ERROR, v); } Error = true; printf("%s:%i - Unknown compression '%i'\n", __FILE__, __LINE__, Comp); break; } } } if (Bits == 1) { // Unpack bits into 8 bit pixels for (int y=0; yX()-1; x >= 0; x--) { i[x] = ( i[x >> 3] & (1 << (7 - (x & 7))) ) ? 0 : 0xff; } } } else if (B == 32 && Bits == 24) { // Upconvert Rgb24 to Rgb32 for (int y=0; yX() - 1; s += pDC->X() - 1; while (d >= e) { d->r = s->r; d->g = s->g; d->b = s->b; d->a = 255; s--; d--; } } } if (Interpretation == PHOTOMETRIC_RGB && pDC->GetBits() >= 24) { if (pDC->GetBits() == 24) { class Tiff24 { public: - uint8 r, g, b; + uint8_t r, g, b; }; // swap R and B for (int y=0; yX() - 1; s += pDC->X() - 1; while (d >= e) { Tiff24 t = *s; d->r = t.r; d->g = t.g; d->b = t.b; s--; d--; } } } else if (pDC->GetBits() == 32) { class Tiff32 { public: - uint8 r, g, b, a; + uint8_t r, g, b, a; }; // swap R and B for (int y=0; yX() - 1; s += pDC->X() - 1; while (d >= e) { Tiff32 t = *s; d->r = t.r; d->g = t.g; d->b = t.b; s--; d--; } } } } if (Interpretation == PHOTOMETRIC_CMYK && pDC->GetBits() == 32) { class TiffCmyk { public: - uint8 c, m, y, k; + uint8_t c, m, y, k; }; // 32 bit CMYK -> RGBA conversion uchar *Div = Div255Lut; for (int y=0; yX(); TiffCmyk *s = (TiffCmyk*) (*pDC)[y]; while (d < e) { int C = Div[s->c * (255 - s->k)] + s->k; int M = Div[s->m * (255 - s->k)] + s->k; int Y = Div[s->y * (255 - s->k)] + s->k; d->r = 255 - C; d->g = 255 - M; d->b = 255 - Y; d->a = 255; s++; d++; } } } IFD *Predictor = FindTag(TAG_Predictor); if (Predictor && Predictor->Value() == 2) { switch (Bits) { case 8: { for (int y=0; yX(); x++, d++) { d[0] += d[-1]; } } break; } case 24: { for (int y=0; yX(); x++, d+=3) { d[0] += d[-3]; d[1] += d[-2]; d[2] += d[-1]; } } break; } } } break; } default: { if (Props) { char Msg[256]; sprintf(Msg, "Image currently doesn't support %i bit TIFF files", Bits); GVariant v = Msg; Props->SetValue(LGI_FILTER_ERROR, v); } Error = true; break; } } Status = Error ? IoError : IoSuccess; } else { printf("%s:%i - Tag missing Compression=%p RowsPerStrip=%p StripOffsets=%p StripByteCounts=%p.\n", __FILE__, __LINE__, Compression, RowsPerStrip, StripOffsets, StripByteCounts); } } else { if (Props) { char Msg[256]; sprintf(Msg, "Couldn't create bitmap of size %ix%i @ %i bpp.", X, Y, B); GVariant v = Msg; Props->SetValue(LGI_FILTER_ERROR, v); } Error = true; } s->SetPos(Pos); return Status; } GFilter::IoStatus GdcTiff::ReadImage(GSurface *pDC, GStream *In) { GFilter::IoStatus Status = IoError; ushort n16; if (!pDC || !In) return Status; s = In; In->Read(&n16, sizeof(n16)); if (n16 == 0x4D4D) { // file is big endian so we swap // Intel is little endian (i think) // TODO_PPC: change this LgiTrace("%s:%i - TIFF read failed 'big endian' file.\n", _FL); return IoError; } Read(&n16, sizeof(n16)); if (n16 == 42) // check magic number { bool Done = false; bool Error = false; ulong Offset; Read(&Offset, sizeof(Offset)); // loop through all the IFD's while ( !Done && !Error) { if (Offset == 0) { Done = true; break; } else { In->SetPos(Offset); ushort IFDEntries; Read(&IFDEntries, sizeof(IFDEntries)); for (int i=0; iRead(*this); } else { Error = true; } } } if (!Error) { // process sub file Status = ProcessRead(pDC); Error = !Status; } else { printf("%s:%i - error reading the IFD's.\n", __FILE__, __LINE__); } Read(&Offset, sizeof(Offset)); } } else { printf("%s:%i - magic number check failed.\n", __FILE__, __LINE__); } return Status; } GFilter::IoStatus GdcTiff::ProcessWrite(GSurface *pDC) { return IoUnsupportedFormat; } GFilter::IoStatus GdcTiff::WriteImage(GStream *Out, GSurface *pDC) { return ProcessWrite(pDC); } diff --git a/src/common/Gdc2/GIcc.cpp b/src/common/Gdc2/GIcc.cpp --- a/src/common/Gdc2/GIcc.cpp +++ b/src/common/Gdc2/GIcc.cpp @@ -1,1188 +1,1188 @@ /** \file \author Matthew Allen \brief Colour management class */ /* This class requires the little cms library to do the underlying work of colour conversion. It does however partially parse the ICC profile data and extract the information into a DOM structure to allow some sort of access. If you don't have little cms then change USE_LCMS to 0. If you want to add support for a new tag, first implement a structure to mimic the format of the tag like the struct IccDescTag. Then add a case for the tag to TagDom::TagDom(...) that instances a new DOM object to display the contents of your struct. */ #ifdef WIN32 #define USE_LCMS 0 #else #define USE_LCMS 0 #endif #include #include "Lgi.h" #include "GIcc.h" #if USE_LCMS #include "lcms.h" #endif /////////////////////////////////////////////////////////////////////////////// -uint32 Swap32(uint32 i) +uint32_t Swap32(uint32_t i) { #if LGI_LITTLE_ENDIAN return ((i & 0xff000000) >> 24) | ((i & 0x00ff0000) >> 8) | ((i & 0x0000ff00) << 8) | ((i & 0x000000ff) << 24); #else return i; #endif } uint16 Swap16(uint16 i) { #if LGI_LITTLE_ENDIAN return ((i & 0xff00) >> 8) | ((i & 0x00ff) << 8); #else return i; #endif } enum IccColourSpace { IccCsXYZ = 0x58595A20, // 'XYZ ' IccCslab = 0x4C616220, // 'Lab ' IccCsluv = 0x4C757620, // 'Luv ' IccCsYCbCr = 0x59436272, // 'YCbr' IccCsYxy = 0x59787920, // 'Yxy ' IccCsRgb = 0x52474220, // 'RGB ' IccCsGray = 0x47524159, // 'GRAY' IccCsHsv = 0x48535620, // 'HSV ' IccCsHls = 0x484C5320, // 'HLS ' IccCsCmyk = 0x434D594B, // 'CMYK' IccCsCmy = 0x434D5920, // 'CMY ' IccCs2Colour = 0x32434C52, // '2CLR' IccCs3Colour = 0x33434C52, // '3CLR' IccCs4Colour = 0x34434C52, // '4CLR' IccCs5Colour = 0x35434C52, // '5CLR' IccCs6Colour = 0x36434C52, // '6CLR' IccCs7Colour = 0x37434C52, // '7CLR' IccCs8Colour = 0x38434C52, // '8CLR' IccCs9Colour = 0x39434C52, // '9CLR' IccCs10Colour = 0x41434C52, // 'ACLR' IccCs11Colour = 0x42434C52, // 'BCLR' IccCs12Colour = 0x43434C52, // 'CCLR' IccCs13Colour = 0x44434C52, // 'DCLR' IccCs14Colour = 0x45434C52, // 'ECLR' IccCs15Colour = 0x46434C52, // 'FCLR' }; enum IccProfileClass { IccProfileInputDevice = 0x73636E72, // 'scnr' IccProfileDisplay = 0x6D6E7472, // 'mntr' IccProfileOutput = 0x70727472, // 'prtr' IccProfileDeviceLink = 0x6C696E6B, // 'link' IccProfileColorSpaceConversion = 0x73706163, // 'spac' IccProfileAbstract = 0x61627374, // 'abst' IccProfileNamed = 0x6E6D636C, // 'nmcl' }; struct S15Fixed16 { int16 i; uint16 f; double d() { return i + ((double)f/0xffff); } }; struct U16Fixed16 { uint16 i; uint16 f; double d() { return i + ((double)f/0xffff); } }; struct U1Fixed15 { uint16 i:1; uint16 f:15; double d() { return i + ((double)f/0x7fff); } }; struct U8Fixed8 { - uint8 i; - uint8 f; + uint8_t i; + uint8_t f; double d() { return i + ((double)f/0xff); } }; struct IccDateTime { uint16 Year; uint16 Month; uint16 Day; uint16 Hour; uint16 Minute; uint16 Second; }; struct IccResponse16Number { uint16 Number; uint16 Reserved; S15Fixed16 Value; }; struct IccXYZ { S15Fixed16 x; S15Fixed16 y; S15Fixed16 z; }; struct IccProfileHeader { - uint32 ProfileSize; - uint32 PreferedCmmType; - uint32 Version; + uint32_t ProfileSize; + uint32_t PreferedCmmType; + uint32_t Version; IccProfileClass Class; IccColourSpace InputSpace; - uint32 ConnectionSpace; + uint32_t ConnectionSpace; IccDateTime CreationDate; - uint32 Magic; // 'acsp' - uint32 PlatformSig; - uint32 Flags; - uint32 DeviceManufacturer; - uint32 DeviceModel; - uint8 DeviceAttributes[8]; - uint32 RenderingIntent; + uint32_t Magic; // 'acsp' + uint32_t PlatformSig; + uint32_t Flags; + uint32_t DeviceManufacturer; + uint32_t DeviceModel; + uint8_t DeviceAttributes[8]; + uint32_t RenderingIntent; IccXYZ PcsD50; - uint32 CreatorSig; + uint32_t CreatorSig; char ProfileId[16]; - uint8 Reserved[28]; + uint8_t Reserved[28]; }; struct IccTag { - uint32 Sig; - uint32 Offset; - uint32 Size; + uint32_t Sig; + uint32_t Offset; + uint32_t Size; }; struct IccTagTable { - uint32 Tags; + uint32_t Tags; IccTag Tag[1]; }; struct IccLocalString { uint16 Lang; uint16 Country; - uint32 Len; - uint32 Offset; + uint32_t Len; + uint32_t Offset; }; struct IccMultiLocalUnicode { - uint32 Sig; - uint32 Reserved; - uint32 Count; - uint32 RecordSize; + uint32_t Sig; + uint32_t Reserved; + uint32_t Count; + uint32_t RecordSize; IccLocalString Str[1]; }; struct IccDescTag { - uint32 Sig; - uint32 Reserved; - uint32 Size; + uint32_t Sig; + uint32_t Reserved; + uint32_t Size; char String[1]; bool IsOk() { return Swap32(Sig) == 'desc'; } }; struct NamedColour { char Name[32]; uint16 PcsCoords[3]; uint16 Coords[1]; // variable NamedColour *Next(int Ch) { return (NamedColour*) ( ((char*)this) + sizeof(NamedColour) + ((Ch-1) * sizeof(uint16)) ); } }; struct IccNameColourTag { - uint32 Sig; - uint32 Reserved; - uint32 Flags; - uint32 Count; - uint32 Coords; + uint32_t Sig; + uint32_t Reserved; + uint32_t Flags; + uint32_t Count; + uint32_t Coords; char Prefix[32]; char Suffix[32]; NamedColour Colours[1]; bool IsOk() { return Swap32(Sig) == 'ncl2'; } }; struct IccTextTag { - uint32 Sig; - uint32 Reserved; + uint32_t Sig; + uint32_t Reserved; char String[1]; bool IsOk() { return Swap32(Sig) == 'text'; } }; struct IccCurve { - uint32 Sig; - uint32 Reserved; - uint32 Count; + uint32_t Sig; + uint32_t Reserved; + uint32_t Count; uint16 Values[1]; bool IsOk() { return Swap32(Sig) == 'curv'; } }; struct IccXYZTag { - uint32 Sig; - uint32 Reserved; + uint32_t Sig; + uint32_t Reserved; IccXYZ Values[1]; bool IsOk() { return Swap32(Sig) == 'XYZ '; } }; class ValueDom : public GDom { char *Txt; public: - ValueDom(uint32 i, const char *name) + ValueDom(uint32_t i, const char *name) { char s[256]; - uint32 is = i; + uint32_t is = i; sprintf(s, "%s: %i (%4.4s)", name, Swap32(i), (char*)&is); Txt = NewStr(s); } ValueDom(IccProfileClass i, const char *name) { char s[256]; - uint32 is = i; + uint32_t is = i; sprintf(s, "%s: %i (%4.4s)", name, Swap32(i), (char*)&is); Txt = NewStr(s); } ValueDom(IccColourSpace i, const char *name) { char s[256]; - uint32 is = i; + uint32_t is = i; sprintf(s, "%s: %i (%4.4s)", name, Swap32(i), (char*)&is); Txt = NewStr(s); } ValueDom(uint16 i, const char *name) { char s[256]; sprintf(s, "%s: %i", name, Swap16(i)); Txt = NewStr(s); } ValueDom(const char *str, const char *name) { char s[256] = "(error)"; if (name && str) sprintf(s, "%s: %s", name, str); else if (name) strcpy(s, name); else if (str) strcpy(s, str); Txt = NewStr(s); } ValueDom(IccDateTime &d, const char *name) { char s[256]; sprintf(s, "%s: %i/%i/%i %i:%i:%i", name, Swap16(d.Day), Swap16(d.Month), Swap16(d.Year), Swap16(d.Hour), Swap16(d.Minute), Swap16(d.Second)); Txt = NewStr(s); } ~ValueDom() { DeleteArray(Txt); } bool GetVariant(const char *Name, GVariant &Value, char *Array) { if (!Name) return false; if (stricmp(Name, "Text") == 0) { Value = Txt; } else return false; return true; } }; #define DomAdd(fld) \ Dom.Add(new ValueDom(h->fld, #fld)); class LocalStringDom : public GDom { char *Base; IccLocalString *Str; public: LocalStringDom(char *b, IccLocalString *s) { Base = b; Str = s; } bool GetVariant(const char *Name, GVariant &Value, char *Array) { if (!Name) return false; if (stricmp(Name, "Text") == 0) { int Off = Swap32(Str->Offset); int Len = Swap32(Str->Len); char16 *u = NewStrW((char16*)(Base+Off), Len); if (!u) return false; for (char16 *p = u; *p; p++) *p = Swap16(*p); char *u8 = WideToUtf8(u); DeleteArray(u); char s[512]; sprintf(s, "'%2.2s' = '%s'", (char*)&Str->Lang, u8); Value = "Localized Unicode"; } else return false; return true; } }; class LocalUnicodeDom : public GDom { IccMultiLocalUnicode *h; GArray Dom; public: LocalUnicodeDom(IccMultiLocalUnicode *header) { h = header; if (Swap32(h->Sig) == 'mluc') { for (int i=0; iCount); i++) { Dom.Add(new LocalStringDom((char*)header, h->Str + i)); } } else { Dom.Add(new ValueDom("Incorrect signature", "Error")); } } ~LocalUnicodeDom() { Dom.DeleteObjects(); } bool GetVariant(const char *Name, GVariant &Value, char *Array) { if (!Name) return false; if (stricmp(Name, "Children") == 0) { Value.SetList(); for (int i=0; iInsert(new GVariant(Dom[i])); } } else if (stricmp(Name, "Text") == 0) { Value = "Localized Unicode"; } else return false; return true; } }; class TagDom : public GDom { IccTag *h; GArray Dom; char *Txt; public: TagDom(IccTag *tag, char *header); ~TagDom(); bool GetVariant(const char *Name, GVariant &Value, char *Array); }; class HeaderDom : public GDom { IccProfileHeader *h; GArray Dom; public: HeaderDom(IccProfileHeader *header) { h = header; DomAdd(ProfileSize); DomAdd(PreferedCmmType); DomAdd(Version); DomAdd(Class); DomAdd(InputSpace); DomAdd(ConnectionSpace); DomAdd(CreationDate); DomAdd(PlatformSig); DomAdd(Flags); DomAdd(DeviceManufacturer); DomAdd(DeviceModel); DomAdd(RenderingIntent); DomAdd(CreatorSig); // DomAdd(ProfileId); } ~HeaderDom() { Dom.DeleteObjects(); } bool GetVariant(const char *Name, GVariant &Value, char *Array) { if (!Name) return false; if (stricmp(Name, "Children") == 0) { Value.SetList(); for (int i=0; iInsert(new GVariant(Dom[i])); } } else if (stricmp(Name, "Text") == 0) { Value = "Header"; } else return false; return true; } }; class CurveDom : public GDom { IccCurve *c; public: CurveDom(IccCurve *curve) { c = curve; } bool GetVariant(const char *Name, GVariant &Value, char *Array) { if (!Name) return false; if (stricmp(Name, "Text") == 0) { char s[256]; int Count = Swap32(c->Count); if (Count == 0) { sprintf(s, "Identity Curve"); } else if (Count == 1) { U8Fixed8 p = *((U8Fixed8*)c->Values); sprintf(s, "Gamma Curve: %f", p.d()); } else { sprintf(s, "Parametric Curve with %i points", Count); } Value = s; } else return false; return true; } }; class XyzDom : public GDom { IccXYZTag *x; int Len; public: XyzDom(IccXYZTag *xyz, int len) { x = xyz; Len = Swap32(len); } bool GetVariant(const char *Name, GVariant &Value, char *Array) { if (!Name) return false; if (stricmp(Name, "Text") == 0) { char s[1024] = ""; char *p = s; IccXYZ *e = (IccXYZ*) (((char*)x) + Len); for (IccXYZ *v = x->Values; v < e; v++) { if (p > s + sizeof(s) - 64) break; sprintf(p, "%f,%f,%f ", v->x.d(), v->y.d(), v->z.d()); p += strlen(p); } Value = s; } else return false; return true; } }; class ChildDom : public GDom { public: GArray Dom; char *Txt; ChildDom() { Txt = 0; } ~ChildDom() { DeleteArray(Txt); } bool GetVariant(const char *Name, GVariant &Value, char *Array) { if (!Name) return false; if (stricmp(Name, "Text") == 0) { Value = Txt; } else if (stricmp(Name, "Children") == 0) { Value.SetList(); for (int i=0; iInsert(new GVariant(Dom[i])); } } else if (stricmp(Name, "Expand") == 0) { Value = false; } else return false; return true; } }; class NclDom : public ChildDom { IccNameColourTag *n; int Len; public: NclDom(IccNameColourTag *named, int len) { n = named; n->Count = Swap32(n->Count); n->Coords = Swap32(n->Coords); Len = Swap32(len); char s[1024] = ""; sprintf(s, "Named colour count: %i", n->Count); Txt = NewStr(s); NamedColour *c = n->Colours; int Block = 100; for (int i=0; iCount; i+=Block) { ChildDom *v = new ChildDom; if (v) { int End = MIN(i + Block - 1, n->Count - 1); Dom.Add(v); sprintf(s, "%i-%i", i, End); v->Txt = NewStr(s); for (int k=0; kName, (double)Swap16(c->PcsCoords[0]) / 0xffff, (double)Swap16(c->PcsCoords[1]) / 0xffff, (double)Swap16(c->PcsCoords[2]) / 0xffff); v->Dom.Add(new ValueDom(s, "Name")); c = c->Next(n->Coords); } } } } }; TagDom::TagDom(IccTag *tag, char *header) { Txt = 0; h = tag; DomAdd(Offset); DomAdd(Size); int Off = Swap32(h->Offset); - uint32 *Ptr = (uint32*) (header + Off); + uint32_t *Ptr = (uint32_t*) (header + Off); switch (Swap32(h->Sig)) { case 'desc': { if (Swap32(*Ptr) == 'mluc') { Dom.Add(new LocalUnicodeDom((IccMultiLocalUnicode*)(header + Off ))); } else if (Swap32(*Ptr) == 'desc') { IccDescTag *Tag = (IccDescTag*) Ptr; Dom.Add(new ValueDom(Txt = Tag->String, "Description")); } break; } case 'cprt': { IccTextTag *Tag = (IccTextTag*)Ptr; if (Tag->IsOk()) { Dom.Add(new ValueDom(Txt = Tag->String, "Copyright")); } break; } case 'rTRC': case 'gTRC': case 'bTRC': { IccCurve *c = (IccCurve*) Ptr; if (c->IsOk()) { Dom.Add(new CurveDom(c)); } break; } case 'rXYZ': case 'gXYZ': case 'bXYZ': { IccXYZTag *Xyz = (IccXYZTag*) Ptr; if (Xyz->IsOk()) { Dom.Add(new XyzDom(Xyz, h->Size)); } break; } case 'ncl2': { IccNameColourTag *Ncl = (IccNameColourTag*) Ptr; if (Ncl->IsOk()) { Dom.Add(new NclDom(Ncl, h->Size)); } break; } } } TagDom::~TagDom() { Dom.DeleteObjects(); } bool TagDom::GetVariant(const char *Name, GVariant &Value, char *Array) { if (!Name) return false; if (stricmp(Name, "Children") == 0) { Value.SetList(); for (int i=0; iInsert(new GVariant(Dom[i])); } } else if (stricmp(Name, "Text") == 0) { char s[256]; sprintf(s, "Tag '%4.4s'", (char*)&h->Sig); Value = s; } else if (stricmp(Name, "Expand") == 0) { Value = (int)(Dom.Length() > 2); } else if (Txt && stricmp(Name, "Name") == 0) { Value = Txt; } else return false; return true; } class TagTableDom : public GDom { IccTagTable *t; GArray Dom; public: TagTableDom(IccTagTable *table, char *header) { t = table; for (int i=0; iTags); i++) { Dom.Add(new TagDom(t->Tag + i, header)); } } ~TagTableDom() { Dom.DeleteObjects(); } bool GetVariant(const char *Name, GVariant &Value, char *Array) { if (!Name) return false; if (stricmp(Name, "Children") == 0) { Value.SetList(); for (int i=0; iInsert(new GVariant(Dom[i])); } } else if (stricmp(Name, "Text") == 0) { Value = "Tag Table"; } else if (stricmp(Name, "Name") == 0) { for (int i=0; iTags; i++) { if (Swap32(t->Tag[i].Sig) == 'desc') { Value = Dom[i]; break; } } } else return false; return true; } }; /////////////////////////////////////////////////////////////////////////////// class GIccProfilePrivate { public: char *Err; char *Name; int64 Len; char *Data; #if USE_LCMS cmsHPROFILE Profile; #endif GArray Dom; GIccProfilePrivate() { Err = 0; Data = 0; Len = 0; Name = 0; #if USE_LCMS Profile = 0; #endif LgiAssert(sizeof(IccProfileHeader) == 128); } ~GIccProfilePrivate() { Empty(); } IccProfileHeader *Header() { return (IccProfileHeader*)Data; } IccTagTable *TagTable() { return Data ? (IccTagTable*)(Data + sizeof(IccProfileHeader)) : 0; } void Empty() { #if USE_LCMS if (Profile) { cmsCloseProfile(Profile); Profile = 0; } #endif Dom.DeleteObjects(); DeleteArray(Err); DeleteArray(Name); DeleteArray(Data); Len = 0; } - bool GetTag(uint32 Tag, char *&Ptr, int &Size) + bool GetTag(uint32_t Tag, char *&Ptr, int &Size) { IccTagTable *t = TagTable(); if (t) { for (int i=0; iTags; i++) { if (t->Tag[i].Sig == Tag) { Ptr = Data + t->Tag[i].Offset; Size = t->Tag[i].Size; return true; } } } return false; } void SetErr(const char *e) { DeleteArray(Err); Err = NewStr(e); } }; /////////////////////////////////////////////////////////////////////////////// GIccProfile::GIccProfile(char *file) { d = new GIccProfilePrivate; } GIccProfile::~GIccProfile() { DeleteObj(d); } bool GIccProfile::CreateNamed(const char *name) { if (name) { #if USE_LCMS d->Empty(); if (stricmp(name, "sRGB") == 0) { d->Profile = cmsCreate_sRGBProfile(); } if (d->Profile) { return true; } #endif } return false; } bool GIccProfile::Open(char *file) { GFile f; if (file && f.Open(file, O_READ)) { return Open(&f); } return false; } bool GIccProfile::Open(GStream *Stream) { if (Stream) { d->Empty(); d->Len = Stream->GetSize(); d->Data = new char[d->Len]; if (d->Data) { if (Stream->Read(d->Data, d->Len) == d->Len) { IccProfileHeader *h = d->Header(); if (Swap32(h->Magic) == 'acsp') { #if USE_LCMS d->Profile = cmsOpenProfileFromMem(d->Data, d->Len); #endif d->Dom.Add(new HeaderDom(d->Header())); d->Dom.Add(new TagTableDom(d->TagTable(), d->Data )); GVariant Desc; if (GetValue("Children[1].Name.Name", Desc)) { d->Name = NewStr(Desc.Str()); } return true; } else { d->SetErr("Not a valid profile."); } } else { d->SetErr("Failed to read stream."); } } else { d->SetErr("Mem alloc failed."); } } else { d->SetErr("Invalid parameter."); } return false; } bool GIccProfile::Save(char *file) { GFile f; if (f.Open(file, O_WRITE)) { f.SetSize(0); return Save(&f); } return false; } bool GIccProfile::Save(GStream *stream) { if (stream) { if (d->Data && d->Len > 0) { return stream->Write(d->Data, d->Len) == d->Len; } } return false; } char *GIccProfile::GetError() { return d->Err; } char *GIccProfile::GetName() { return d->Name; } bool GIccProfile::Convert(COLOUR *Out32, COLOUR In32, GIccProfile *Profile) { return false; } bool GIccProfile::Convert(GSurface *Dest, GSurface *Src, GIccProfile *Profile) { #if USE_LCMS if (!Dest || !Src || !Profile) { d->SetErr("Invalid parameter(s)."); return false; } if (Dest->X() != Src->X() || Dest->Y() != Src->Y()) { d->SetErr("Source and Dest images are different sizes."); return false; } if ( ! ( (Dest->GetBits() == 32 && Src->GetBits() == 32) || (Dest->GetBits() == 24 && Src->GetBits() == 24) ) ) { d->SetErr("Must be RGB or RGBA images."); return false; } if (!d->Profile) { d->SetErr("Dest profile error."); return false; } if (!Profile->d->Profile) { d->SetErr("Src profile error."); return false; } cmsHTRANSFORM t = cmsCreateTransform( Profile->d->Profile, Src->GetBits() == 32 ? TYPE_BGRA_8 : TYPE_BGR_8, d->Profile, Src->GetBits() == 32 ? TYPE_BGRA_8 : TYPE_BGR_8, d->Header() ? d->Header()->RenderingIntent : 0, 0); if (t) { uchar *Buf = 0; int Bytes = 0; if (Src == Dest) { Bytes = (Src->GetBits() / 8) * Src->X(); Buf = new uchar[Bytes]; } for (int y=0; yY(); y++) { uchar *sp = (*Src)[y]; uchar *dp = (*Dest)[y]; if (sp && dp) { if (Buf) { memcpy(Buf, sp, Bytes); } cmsDoTransform(t, Buf ? Buf : sp, dp, Src->X()); } } DeleteArray(Buf); cmsDeleteTransform(t); return true; } else { d->SetErr("Couldn't create colour transform."); } #else d->SetErr("LCMS support not compiled in."); #endif return false; } bool GIccProfile::GetVariant(const char *Name, GVariant &Value, char *Array) { if (!Name) return false; if (stricmp(Name, "Children") == 0) { if (Array) { Value = d->Dom[atoi(Array)]; } else { Value.SetList(); for (int i=0; iDom.Length(); i++) { Value.Value.Lst->Insert(new GVariant(d->Dom[i])); } } } else if (stricmp(Name, "Text") == 0) { Value = "ICC Colour Profile"; } else return false; return true; } diff --git a/src/common/Gdc2/Tools/GdcTools.cpp b/src/common/Gdc2/Tools/GdcTools.cpp --- a/src/common/Gdc2/Tools/GdcTools.cpp +++ b/src/common/Gdc2/Tools/GdcTools.cpp @@ -1,829 +1,829 @@ /* ** FILE: DcTools.cpp ** AUTHOR: Matthew Allen ** DATE: 3/8/98 ** DESCRIPTION: Device context tools ** ** Copyright (C) 1998-2001, Matthew Allen ** fret@memecode.com */ #include #include "Gdc2.h" #include "GdcTools.h" #include "GPalette.h" bool IsGreyScale(GSurface *pDC) { bool Status = false; if (pDC) { GPalette *Pal = pDC->Palette(); Status = true; if (Pal && Pal->GetSize() > 0) { for (int i=0; iGetSize(); i++) { GdcRGB *p = (*Pal)[i]; if (p) { int Grey = i * 255 / Pal->GetSize(); if (p->r != Grey || p->g != Grey || p->b != Grey) { Status = false; } } } } } return Status; } #define FP_RED_TO_GREY 19595 // 0.299 * 65536 #define FP_GREEN_TO_GREY 38469 // 0.587 * 65536 #define FP_BLUE_TO_GREY 7471 // 0.114 * 65536 bool GreyScaleDC(GSurface *pDest, GSurface *pSrc) { bool Status = false; if (pDest && pSrc) { switch (pSrc->GetBits()) { case 8: { // 8 -> 8 greyscale convert if (pDest->Create(pSrc->X(), pSrc->Y(), CsIndex8)) { GPalette *Pal = pSrc->Palette(); if (Pal) { // convert palette uchar Map[256]; ZeroObj(Map); for (int i=0; iGetSize(); i++) { // Weights, r:.299, g:.587, b:.114 GdcRGB *n = (*Pal)[i]; if (n) { - uint32 r = ((uint32) n->r << 16) * FP_RED_TO_GREY; - uint32 g = ((uint32) n->g << 16) * FP_GREEN_TO_GREY; - uint32 b = ((uint32) n->b << 16) * FP_BLUE_TO_GREY; + uint32_t r = ((uint32_t) n->r << 16) * FP_RED_TO_GREY; + uint32_t g = ((uint32_t) n->g << 16) * FP_GREEN_TO_GREY; + uint32_t b = ((uint32_t) n->b << 16) * FP_BLUE_TO_GREY; Map[i] = (r + g + b) >> 16; n->r = n->g = n->b = i; } } Pal->Update(); pDest->Palette(Pal, true); Status = true; // convert pixels.. for (int y=0; yY(); y++) { uchar *s = (*pSrc)[y]; uchar *d = (*pDest)[y]; for (int x=0; xX(); x++) { d[x] = Map[s[x]]; } } } } break; } case 16: case 24: case 32: { if (pDest->Create(pSrc->X(), pSrc->Y(), CsIndex8)) { uchar RMap[256]; uchar GMap[256]; uchar BMap[256]; for (int i=0; i<256; i++) { RMap[i] = ((i << 16) * FP_RED_TO_GREY) >> 16; GMap[i] = ((i << 16) * FP_GREEN_TO_GREY) >> 16; BMap[i] = ((i << 16) * FP_BLUE_TO_GREY) >> 16; } int SBits = pSrc->GetBits(); for (int y=0; yY(); y++) { switch (SBits) { case 16: { ushort *Src = (ushort*) (*pSrc)[y]; uchar *Dest = (*pDest)[y]; for (int x=0; xX(); x++) { uchar r = R16(Src[x]) << 3; uchar g = G16(Src[x]) << 2; uchar b = B16(Src[x]) << 3; Dest[x] = RMap[r] + GMap[g] + BMap[b]; } break; } case 24: { uchar *Src = (*pSrc)[y]; uchar *Dest = (*pDest)[y]; for (int x=0; xX(); x++) { uchar *x3 = Src + ((x<<1) + x); Dest[x] = RMap[ x3[2] ] + GMap[ x3[1] ] + BMap[ x3[0] ]; } break; } case 32: { ulong *Src = (ulong*) (*pSrc)[y]; uchar *Dest = (*pDest)[y]; for (int x=0; xX(); x++) { uchar r = R32(Src[x]); uchar g = G32(Src[x]); uchar b = B32(Src[x]); Dest[x] = RMap[r] + GMap[g] + BMap[b]; } break; } } } GPalette *Pal = new GPalette(0, 256); if (Pal) { // convert palette for (int i=0; iGetSize(); i++) { GdcRGB *n = (*Pal)[i]; if (n) { n->r = n->g = n->b = i; } } pDest->Palette(Pal); Status = true; } } break; } } } return Status; } bool InvertDC(GSurface *pDC) { bool Status = true; if (pDC) { switch (pDC->GetBits()) { case 8: { if (IsGreyScale(pDC)) { for (int y=0; yY(); y++) { uchar *d = (*pDC)[y]; if (d) { for (int x=0; xX(); x++) { d[x] = 255 - d[x]; } } } Status = true; } else { GPalette *Pal = pDC->Palette(); if (Pal) { GdcRGB *p = (*Pal)[0]; if (p) { for (int i=0; iGetSize(); i++) { p[i].r = 255 - p[i].r; p[i].g = 255 - p[i].g; p[i].b = 255 - p[i].b; } Status = true; } } } break; } case 16: { for (int y=0; yY(); y++) { ushort *d = (ushort*) (*pDC)[y]; if (d) { for (int x=0; xX(); x++) { d[x] = ((31 - R16(d[x])) << 11) | ((63 - G16(d[x])) << 5) | ((31 - B16(d[x]))); } } } break; } case 24: { for (int y=0; yY(); y++) { uchar *d = (*pDC)[y]; if (d) { for (int x=0; xX(); x++) { *d = 255 - *d; d++; *d = 255 - *d; d++; *d = 255 - *d; d++; } } } break; } case 32: { for (int y=0; yY(); y++) { uchar *d = (*pDC)[y]; if (d) { for (int x=0; xX(); x++) { *d = 255 - *d; d++; *d = 255 - *d; d++; *d = 255 - *d; d++; d++; // leave alpha channel alone } } } break; } } } return Status; } bool FlipDC(GSurface *pDC, int Dir) { bool Status = false; if (pDC) { switch (Dir) { case FLIP_X: { for (int y=0; yY(); y++) { for (int x=0, nx=pDC->X()-1; xGet(x, y); pDC->Colour(pDC->Get(nx, y)); pDC->Set(x, y); pDC->Colour(c); pDC->Set(nx, y); } } Status = true; break; } case FLIP_Y: { for (int y=0, ny=pDC->Y()-1; yX(); x++) { COLOUR c = pDC->Get(x, y); pDC->Colour(pDC->Get(x, ny)); pDC->Set(x, y); pDC->Colour(c); pDC->Set(x, ny); } } Status = true; break; } } } return Status; } bool RotateDC(GSurface *pDC, double Angle, Progress *Prog) { if (!pDC) return false; GAutoPtr pOld(new GMemDC(pDC)); // do the rotation if (Angle == 180) { GSurface *pOldAlpha = pOld->AlphaDC(); GSurface *pAlpha = pDC->AlphaDC(); for (int y=0; yY() && (!Prog || !Prog->IsCancelled()); y++) { for (int x=0; xX(); x++) { pDC->Colour(pOld->Get(x, y)); pDC->Set(pOld->X()-x-1, pOld->Y()-y-1); if (pOldAlpha && pAlpha) { pAlpha->Colour(pOldAlpha->Get(x, y)); pAlpha->Set(pOld->X()-x-1, pOld->Y()-y-1); } } } } else if (Angle == 90 || Angle == 270) { if (!pDC->Create(pOld->Y(), pOld->X(), pOld->GetColourSpace())) return false; GSurface *pOldAlpha = pOld->AlphaDC(); if (pOldAlpha) { pDC->HasAlpha(true); } GSurface *pAlpha = pDC->AlphaDC(); if (Angle == 90) { for (int y=0; yY() && (!Prog || !Prog->IsCancelled()); y++) { for (int x=0; xX(); x++) { pDC->Colour(pOld->Get(x, y)); pDC->Set(pDC->X()-y-1, x); if (pOldAlpha && pAlpha) { pAlpha->Colour(pOldAlpha->Get(x, y)); pAlpha->Set(pDC->X()-y-1, x); } } } } else { for (int y=0; yY(); y++) { int Dy = pDC->Y() - 1; for (int x=0; xX() && (!Prog || !Prog->IsCancelled()); x++, Dy--) { pDC->Colour(pOld->Get(x, y)); pDC->Set(y, Dy); if (pOldAlpha && pAlpha) { pAlpha->Colour(pOldAlpha->Get(x, y)); pAlpha->Set(y, Dy); } } } } } else { // free angle return false; } return true; } bool FlipXDC(GSurface *pDC, Progress *Prog) { bool Status = false; if (pDC) { for (int y=0; yY() && (!Prog || !Prog->IsCancelled()); y++) { int Sx = 0; int Ex = pDC->X()-1; for (; SxGet(Sx, y); pDC->Colour(pDC->Get(Ex, y)); pDC->Set(Sx, y); pDC->Colour(c); pDC->Set(Ex, y); } } Status = true; } return Status; } bool FlipYDC(GSurface *pDC, Progress *Prog) { bool Status = false; if (pDC) { for (int x=0; xX() && (!Prog || !Prog->IsCancelled()); x++) { int Sy = 0; int Ey = pDC->Y()-1; for (; SyGet(x, Sy); pDC->Colour(pDC->Get(x, Ey)); pDC->Set(x, Sy); pDC->Colour(c); pDC->Set(x, Ey); } } Status = true; } return Status; } // Remaps DC to new palette bool RemapDC(GSurface *pDC, GPalette *DestPal) { bool Status = false; if (pDC && pDC->GetBits() <= 8 && DestPal) { GPalette *SrcPal = pDC->Palette(); int Colours = (SrcPal) ? SrcPal->GetSize() : 1 << pDC->GetBits(); uchar *Remap = new uchar[Colours]; if (Remap) { if (SrcPal) { for (int i=0; iMatchRgb(Rgb24(p->r, p->g, p->b)); } else { Remap[i] = 0; } } } else { for (int i=0; iMatchRgb(Rgb24(i, i, i)); } } for (int y=0; yY(); y++) { for (int x=0; xX(); x++) { pDC->Colour(Remap[pDC->Get(x, y)]); pDC->Set(x, y); } } Status = true; DeleteArray(Remap); } } return Status; } #if 0 float lerp(float s, float e, float t) { return s+(e-s)*t; } float blerp(float c00, float c10, float c01, float c11, float tx, float ty) { return lerp(lerp(c00, c10, tx), lerp(c01, c11, tx), ty); } bool ResampleDC(GSurface *dst, GSurface *src, GRect *FromRgn, Progress *Prog) { // void scale(image_t *src, image_t *dst, float scalex, float scaley){ int newWidth = dst->X(); int newHeight= dst->Y(); switch (src->GetColourSpace()) { case System32BitColourSpace: { for (int x = 0, y = 0; y < newHeight; x++) { if (x > newWidth) { x = 0; y++; } float gx = x / (float)(newWidth) * (src->X()-1); float gy = y / (float)(newHeight) * (src->Y()-1); int gxi = (int)gx; int gyi = (int)gy; if (gxi + 1 >= src->X() || gyi + 1 >= src->Y()) continue; uint32_t result = 0; #define getpixel(img, x, y) ((System32BitPixel*)(*img)[y])+x System32BitPixel *c00 = getpixel(src, gxi, gyi); System32BitPixel *c10 = getpixel(src, gxi+1, gyi); System32BitPixel *c01 = getpixel(src, gxi, gyi+1); System32BitPixel *c11 = getpixel(src, gxi+1, gyi+1); System32BitPixel *d = getpixel(dst, x, y); #define comp(c) \ d->c = (uint8_t)blerp(c00->c, c10->c, c01->c, c11->c, gx - gxi, gy - gyi); comp(r); comp(g); comp(b); comp(a); } break; } default: LgiAssert(0); } return true; } #else bool ResampleDC(GSurface *pDest, GSurface *pSrc, GRect *FromRgn, Progress *Prog) { if (!pDest || !pSrc) return false; GRect Full(0, 0, pSrc->X()-1, pSrc->Y()-1), Sr; if (FromRgn) { Sr = *FromRgn; Sr.Bound(&Full); } else { Sr = Full; } double ScaleX = (double) Sr.X() / pDest->X(); double ScaleY = (double) Sr.Y() / pDest->Y(); int Sx = (int) (ScaleX * 256.0); int Sy = (int) (ScaleY * 256.0); int SrcBits = pSrc->GetBits(); int OutBits = pDest->GetBits() == 32 ? 32 : 24; RgbLut ToLinear(RgbLutLinear, RgbLutSRGB); - RgbLut ToSRGB(RgbLutSRGB, RgbLutLinear); + RgbLut ToSRGB(RgbLutSRGB, RgbLutLinear); if (Prog) { Prog->SetDescription("Resampling image..."); Prog->SetLimits(0, pDest->Y()-1); } Sr.x1 <<= 8; Sr.y1 <<= 8; Sr.x2 <<= 8; Sr.y2 <<= 8; GPalette *DestPal = pDest->Palette(); System32BitPixel pal8[256]; if (pSrc->GetBits() <= 8) { GPalette *p = pSrc->Palette(); for (int i=0; i<256; i++) { if (p && i < p->GetSize()) { GdcRGB *rgb = (*p)[i]; pal8[i].r = rgb->r; pal8[i].g = rgb->g; pal8[i].b = rgb->b; } else { pal8[i].r = i; pal8[i].g = i; pal8[i].b = i; } pal8[i].a = 255; } } // For each destination pixel GSurface *pAlpha = pSrc->AlphaDC(); GColourSpace SrcCs = pSrc->GetColourSpace(); GColourSpace DstCs = pDest->GetColourSpace(); bool HasSrcAlpha = GColourSpaceHasAlpha(SrcCs); bool HasDestAlpha = GColourSpaceHasAlpha(DstCs); bool ProcessAlpha = HasSrcAlpha && HasDestAlpha; for (int Dy = 0; DyY(); Dy++) { for (int Dx = 0; DxX(); Dx++) { uint64 Area = 0; uint64 R = 0; uint64 G = 0; uint64 B = 0; uint64 A = 0; REG uint64 a; int NextX = (Dx+1)*Sx+Sr.x1; int NextY = (Dy+1)*Sy+Sr.y1; int Nx, Ny; COLOUR c; REG uint16 *lin = ToLinear.Lut; switch (pSrc->GetBits()) { case 32: { for (int y=Dy*Sy+Sr.y1; y> 8]; if (!Src) { LgiAssert(0); break; } Src += x >> 8; a = (Nx - x) * (Ny - y); // Add the colour and area to the running total R += a * lin[Src->r]; G += a * lin[Src->g]; B += a * lin[Src->b]; A += a * lin[Src->a]; Area += a; } } break; } case 8: { for (int y=Dy*Sy+Sr.y1; y> 8; SrcY = y >> 8; c = pSrc->Get(SrcX, SrcY); Alpha = pAlpha->Get(SrcX, SrcY); a = (Nx - x) * (Ny - y); // Add the colour and area to the running total R += a * lin[pal8[c].r]; G += a * lin[pal8[c].g]; B += a * lin[pal8[c].b]; A += a * lin[Alpha]; Area += a; } } else { for (REG int x=Dx*Sx+Sr.x1; xGet(x>>8, y>>8); a = (Nx - x) * (Ny - y); // Add the colour and area to the running total R += a * lin[pal8[c].r]; G += a * lin[pal8[c].g]; B += a * lin[pal8[c].b]; Area += a; } } } break; } default: { for (int y=Dy*Sy+Sr.y1; yGet(x>>8, y>>8), SrcBits); a = (Nx - x) * (Ny - y); // Add the colour and area to the running total R += a * lin[R24(c)]; G += a * lin[G24(c)]; B += a * lin[B24(c)]; Area += a; } } break; } } if (Area) { R /= Area; G /= Area; B /= Area; if (ProcessAlpha) { A /= Area; c = Rgba32( ToSRGB.Lut[R], ToSRGB.Lut[G], ToSRGB.Lut[B], ToSRGB.Lut[A]); } else if (OutBits == 32) { c = Rgb32( ToSRGB.Lut[R], ToSRGB.Lut[G], ToSRGB.Lut[B]); } else { c = Rgb24( ToSRGB.Lut[R], ToSRGB.Lut[G], ToSRGB.Lut[B]); } } else { c = 0; } if (DestPal) { c = DestPal->MatchRgb(c); pDest->Colour(c); } else { pDest->Colour(c, OutBits); } pDest->Set(Dx, Dy); } if (Prog) { Prog->Value(Dy); } } return true; } #endif \ No newline at end of file diff --git a/src/common/INet/LWebSocket.cpp b/src/common/INet/LWebSocket.cpp --- a/src/common/INet/LWebSocket.cpp +++ b/src/common/INet/LWebSocket.cpp @@ -1,469 +1,469 @@ // https://tools.ietf.org/html/rfc6455 #include "Lgi.h" #include "LWebSocket.h" #include "INetTools.h" #include "../../src/common/Hash/sha1/sha1.h" #include "Base64.h" #ifdef LINUX #include #include #include #define htonll LgiSwap64 #endif #define LOG_ALL 0 //////////////////////////////////////////////////////////////////////////// LSelect::LSelect(GSocket *sock) { if (sock) *this += sock; } LSelect &LSelect::operator +=(GSocket *sock) { if (sock) s.Add(sock); return *this; } int LSelect::Select(GArray &Results, bool Rd, bool Wr, int TimeoutMs) { if (s.Length() == 0) return 0; #ifdef LINUX // Because Linux doesn't return from select() when the socket is // closed elsewhere we have to do something different... damn Linux, // why can't you just like do the right thing? ::GArray fds; fds.Length(s.Length()); for (unsigned i=0; iHandle(); fds[i].events = (Wr ? POLLOUT : 0) | (Rd ? POLLIN : 0) | POLLRDHUP | POLLERR; fds[i].revents = 0; } int r = poll(fds.AddressOf(), fds.Length(), TimeoutMs); int Signalled = 0; if (r > 0) { for (unsigned i=0; iHandle()) { // printf("Poll[%i] = %x (flags=%x)\n", i, f.revents, Flags); Results.Add(s[i]); } else LgiAssert(0); } } } return Signalled; #else struct timeval t = {TimeoutMs / 1000, (TimeoutMs % 1000) * 1000}; fd_set r; FD_ZERO(&r); OsSocket Max = 0; for (auto Sock : s) { auto h = Sock->Handle(); if (Max < h) Max = h; FD_SET(h, &r); } int v = select( (int)Max+1, Rd ? &r : NULL, Wr ? &r : NULL, NULL, TimeoutMs >= 0 ? &t : NULL); if (v > 0) { for (auto Sock : s) { if (FD_ISSET(Sock->Handle(), &r)) Results.Add(Sock); } } return v; #endif } GArray LSelect::Readable(int TimeoutMs) { GArray r; Select(r, true, false, TimeoutMs); return r; } GArray LSelect::Writeable(int TimeoutMs) { GArray r; Select(r, false, true, TimeoutMs); return r; } //////////////////////////////////////////////////////////////////////////// enum WebSocketState { WsReceiveHdr, WsMessages, WsClosed, }; enum WsOpCode { WsContinue = 0, WsText = 1, WsBinary = 2, WsClose = 8, WsPing = 9, WsPong = 10, }; struct LWebSocketPriv { LWebSocket *Ws; bool Server; WebSocketState State; GString InHdr, OutHdr; GArray Data; size_t Start, Used; char Buf[512]; GArray Msg; LWebSocket::OnMsg onMsg; LWebSocketPriv(LWebSocket *ws, bool server) : Ws(ws), Server(server) { State = WsReceiveHdr; Used = 0; Start = 0; } template ssize_t Write(T *p, size_t len) { size_t i = 0; while (i < len) { auto Wr = Ws->Write(p + i, len - i); if (Wr <= 0) return i; i += Wr; } return i; } bool AddData(char *p, size_t Len) { #if LOG_ALL LgiTrace("Websocket Read: %i\n", (int)Len); for (unsigned i=0; i=' '?p[i]:' '); s[ch++] = '\n'; s[ch] = 0; LgiTrace("%.*s", ch, s); } #endif if (Used + Len > Data.Length()) Data.Length(Used + Len + 1024); memcpy(Data.AddressOf(Used), p, Len); Used += Len; if (State != WsMessages) return false; return CheckMsg(); } void OnMsg(char *p, uint64 Len) { if (onMsg) onMsg(p, Len); } bool CheckMsg() { // Check for valid message.. if (Used < 2) return false; // too short - uint8 *p = (uint8*)Data.AddressOf(Start); - uint8 *End = p + Used; + uint8_t *p = (uint8_t*)Data.AddressOf(Start); + uint8_t *End = p + Used; bool Fin = (p[0] & 0x80) != 0; WsOpCode OpCode = (WsOpCode) (p[0] & 0xf); bool Masked = (p[1] & 0x80) != 0; uint64 Len = p[1] & 0x7f; if (Len == 126) { if (Used < 4) return false; // Too short Len = (p[2] << 8) | p[3]; p += 4; } else if (Len == 127) { if (Used < 10) return false; // Too short p += 2; Len = ((uint64)p[0] << 54) | ((uint64)p[1] << 48) | ((uint64)p[2] << 40) | ((uint64)p[3] << 32) | ((uint64)p[4] << 24) | ((uint64)p[5] << 16) | ((uint64)p[6] << 8) | p[7]; p += 8; } else p += 2; - uint8 Mask[4]; + uint8_t Mask[4]; if (Masked) { if (p > End - 4) return false; // Too short Mask[0] = *p++; Mask[1] = *p++; Mask[2] = *p++; Mask[3] = *p++; if (End - p < (ssize_t)Len) return false; // Too short // Unmask for (uint64 i=0; i 0) memcpy(Data.AddressOf(), p, Remaining); Used = Remaining; } else LgiAssert(!"Impl me"); } else { auto Pos = (char*)p - Data.AddressOf(); Msg.New().Set(Pos, Len); Start = Pos + Len; } if (OpCode == WsClose) { Ws->Close(); State = WsClosed; } return Status; } bool Error(const char *Msg) { return false; } bool SendResponse() { // Create the response hdr and send it... static const char *Key = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; GAutoString Upgrade(InetGetHeaderField(InHdr, "Upgrade", InHdr.Length())); if (!Upgrade || stricmp(Upgrade, "websocket")) return false; GAutoString SecWebSocketKey(InetGetHeaderField(InHdr, "Sec-WebSocket-Key", InHdr.Length())); if (!SecWebSocketKey) return Error("No Sec-WebSocket-Key header"); GString s = SecWebSocketKey.Get(); s += Key; SHA1Context Ctx; SHA1Reset(&Ctx); SHA1Input(&Ctx, (const uchar*) s.Get(), (unsigned) s.Length()); if (!SHA1Result(&Ctx)) return Error("SHA1Result failed"); - uint32 Digest[5]; + uint32_t Digest[5]; for (int i=0; ionMsg = onMsg; } bool LWebSocket::SendMessage(char *Data, uint64 Len) { if (d->State != WsMessages) return false; - uint8 Masked = d->Server ? 0 : 0x80; + uint8_t Masked = d->Server ? 0 : 0x80; int8 Hdr[2 + 8 + 4]; GPointer p = {Hdr}; *p.u8++ = 0x80 | // Fin WsText; if (Len < 126) // 1 byte { // Direct len - *p.u8++ = Masked | (uint8)Len; + *p.u8++ = Masked | (uint8_t)Len; } else if (Len <= 0xffff) // 2 byte { // 126 + 2 bytes *p.u8++ = Masked | 126; *p.u16++ = htons((u_short)Len); } else { // 127 + 8 bytes *p.u8++ = Masked | 127; *p.u64++ = htonll(Len); } - GAutoPtr MaskData; + GAutoPtr MaskData; if (Masked) { - uint8 *Mask = p.u8; + uint8_t *Mask = p.u8; *p.u32++ = LgiRand(); - if (!MaskData.Reset(new uint8[Len])) + if (!MaskData.Reset(new uint8_t[Len])) return d->Error("Alloc failed."); - uint8 *Out = MaskData.Get(); + uint8_t *Out = MaskData.Get(); for (uint64 i=0; iWrite(Hdr, Sz); if (Wr != Sz) return d->Error("SendMessage.Hdr failed to write to socket"); - Wr = d->Write(MaskData ? MaskData.Get() : (uint8*)Data, Len); + Wr = d->Write(MaskData ? MaskData.Get() : (uint8_t*)Data, Len); if (Wr != Len) return d->Error("SendMessage.Body failed to write to socket"); return true; } bool LWebSocket::InitFromHeaders(GString Data, OsSocket Sock) { bool HasMsg = false; if (d->State == WsReceiveHdr) { d->InHdr = Data; Handle(Sock); auto End = d->InHdr.Find("\r\n\r\n"); if (End >= 0) { if (d->InHdr.Length() > (size_t)End + 4) { d->AddData(d->InHdr.Get() + End + 4, d->InHdr.Length() - End - 4); d->InHdr.Length(End); } HasMsg = d->SendResponse(); } } return HasMsg; } bool LWebSocket::OnData() { bool GotData = false; auto Rd = Read(d->Buf, sizeof(d->Buf)); if (Rd > 0) { if (d->State == WsReceiveHdr) { d->InHdr += GString(d->Buf, Rd); auto End = d->InHdr.Find("\r\n\r\n"); if (End >= 0) { if (d->InHdr.Length() > (size_t)End + 4) { d->AddData(d->InHdr.Get() + End + 4, d->InHdr.Length() - End - 4); d->InHdr.Length(End); } GotData = d->SendResponse(); } } else { GotData = d->AddData(d->Buf, Rd); } } return GotData; } diff --git a/src/common/Images/Compare/ImageComparison.cpp b/src/common/Images/Compare/ImageComparison.cpp --- a/src/common/Images/Compare/ImageComparison.cpp +++ b/src/common/Images/Compare/ImageComparison.cpp @@ -1,1052 +1,1052 @@ #include "Lgi.h" #include "LList.h" #include "GCombo.h" #include "GEdit.h" #include "GPalette.h" #include "GZoomView.h" #include "GVariant.h" #include "GButton.h" #include "ImageComparison.h" #include "GTabView.h" #include "resdefs.h" #include "LgiRes.h" #define DIFF_LARGE_8BIT (10) #define DIFF_LARGE_16BIT (DIFF_LARGE_8BIT << 8) #define M_LOAD (M_USER + 2000) #define OPT_CompareLeft "CmpLeft" #define OPT_CompareRight "CmpRight" enum CmpCtrls { IDC_A_NAME = 1000, IDC_B_NAME, IDC_C_NAME, IDC_A_VIEW, IDC_B_VIEW, IDC_C_VIEW, IDC_TAB_VIEW, IDC_TAB_PAGE, }; #ifdef DIFF_CENTER GArray RDiff, GDiff, BDiff; #endif template -void CompareRgb(GSurface *A, GSurface *B, uint8 *c, GdcPt2 size, int threshold) +void CompareRgb(GSurface *A, GSurface *B, uint8_t *c, GdcPt2 size, int threshold) { if (!A || !B || !c) return; Px *a = (Px*) (*A)[size.y]; Px *b = (Px*) (*B)[size.y]; Px *e = a + size.x; int32 Diff, Mask, Value; while (a < e) { Value = (int32)a->r - b->r; #ifdef DIFF_CENTER RDiff[DIFF_CENTER+Value]++; #endif Mask = Value >> 31; Diff = (Value + Mask) ^ Mask; Value = (int32)a->g - b->g; #ifdef DIFF_CENTER GDiff[DIFF_CENTER+Value]++; #endif Mask = Value >> 31; Diff += (Value + Mask) ^ Mask; Value = (int32)a->b - b->b; #ifdef DIFF_CENTER BDiff[DIFF_CENTER+Value]++; #endif Mask = Value >> 31; Diff += (Value + Mask) ^ Mask; *c++ = (Diff > 0) + (Diff >= threshold); a++; b++; } } template -void CompareRgba(GSurface *A, GSurface *B, uint8 *c, GdcPt2 size, int threshold) +void CompareRgba(GSurface *A, GSurface *B, uint8_t *c, GdcPt2 size, int threshold) { Px *a = (Px*) (*A)[size.y]; Px *b = (Px*) (*B)[size.y]; Px *e = a + size.x; int32 Diff, Mask, Value; while (a < e) { Value = (int32)a->r - b->r; #ifdef DIFF_CENTER RDiff[DIFF_CENTER+Value]++; #endif Mask = Value >> 31; Diff = (Value + Mask) ^ Mask; Value = (int32)a->g - b->g; #ifdef DIFF_CENTER GDiff[DIFF_CENTER+Value]++; #endif Mask = Value >> 31; Diff += (Value + Mask) ^ Mask; Value = (int32)a->b - b->b; #ifdef DIFF_CENTER BDiff[DIFF_CENTER+Value]++; #endif Mask = Value >> 31; Diff += (Value + Mask) ^ Mask; Value = (int32)a->a - b->a; Mask = Value >> 31; Diff += (Value + Mask) ^ Mask; *c++ = (Diff > 0) + (Diff >= threshold); a++; b++; } } GAutoPtr CreateDiff(GViewI *Parent, GSurface *A, GSurface *B) { GAutoPtr C; int Cx = MIN(A->X(), B->X()), Cy = MIN(A->Y(), B->Y()); if (A->GetColourSpace() != B->GetColourSpace()) { GStringPipe p; p.Print("The bit depths of the images are different: %i (left), %i (right).", A->GetBits(), B->GetBits()); GAutoString a(p.NewStr()); LgiMsg(Parent, "%s", "Image Compare", MB_OK, a.Get()); } else if (C.Reset(new GMemDC(Cx, Cy, CsIndex8)) && (*C)[0]) { uchar Pal[] = {0, 0, 0, 0xc0, 0xc0, 0xc0, 0xff, 0, 0}; C->Palette(new GPalette(Pal, 3)); for (int y=0; yGetColourSpace()) { case CsIndex8: { GPalette *apal = A->Palette(); GPalette *bpal = B->Palette(); if (apal && bpal) { GdcRGB *ap = (*apal)[0]; GdcRGB *bp = (*bpal)[0]; - uint8 *a = (*A)[y]; - uint8 *b = (*B)[y]; - uint8 *e = a + Cx; + uint8_t *a = (*A)[y]; + uint8_t *b = (*B)[y]; + uint8_t *e = a + Cx; while (a < e) { Value = (int32)ap[*a].r - bp[*b].r; Mask = Value >> 31; Diff = (Value + Mask) ^ Mask; Value = (int32)ap[*a].g - bp[*b].g; Mask = Value >> 31; Diff += (Value + Mask) ^ Mask; Value = (int32)ap[*a].b - bp[*b].b; Mask = Value >> 31; Diff += (Value + Mask) ^ Mask; *c++ = (Diff > 0) + (Diff >= DIFF_LARGE_8BIT); a++; b++; } } else LgiAssert(!"No palette?"); break; } #define CompareCaseRgb(type, threshold) \ case Cs##type: \ CompareRgb(A, B, c, GdcPt2(Cx, y), threshold); \ break CompareCaseRgb(Rgb24, DIFF_LARGE_8BIT); CompareCaseRgb(Bgr24, DIFF_LARGE_8BIT); CompareCaseRgb(Rgbx32, DIFF_LARGE_8BIT); CompareCaseRgb(Bgrx32, DIFF_LARGE_8BIT); CompareCaseRgb(Xrgb32, DIFF_LARGE_8BIT); CompareCaseRgb(Xbgr32, DIFF_LARGE_8BIT); CompareCaseRgb(Rgb48, DIFF_LARGE_16BIT); CompareCaseRgb(Bgr48, DIFF_LARGE_16BIT); #define CompareCaseRgba(type, threshold) \ case Cs##type: \ CompareRgba(A, B, c, GdcPt2(Cx, y), threshold); \ break CompareCaseRgba(Rgba32, DIFF_LARGE_8BIT); CompareCaseRgba(Bgra32, DIFF_LARGE_8BIT); CompareCaseRgba(Argb32, DIFF_LARGE_8BIT); CompareCaseRgba(Abgr32, DIFF_LARGE_8BIT); CompareCaseRgba(Rgba64, DIFF_LARGE_16BIT); CompareCaseRgba(Bgra64, DIFF_LARGE_16BIT); default: { LgiAssert(!"Impl me."); break; } } } } #ifdef DIFF_CENTER int Len = max(RDiff.Length(), GDiff.Length()); Len = max(Len, BDiff.Length()); int Start = Len-1, End = 0; for (int i=0; i Img; GMessage::Param Param; ThreadLoader(GView *owner, GAutoString file, GMessage::Param param) : LThread("ThreadLoader") { Owner = owner; File = file; Param = param; Run(); } ~ThreadLoader() { while (!IsExited()) LgiSleep(10); } int Main() { // Load the file... Img.Reset(LoadDC(File)); if (!Owner->Handle()) { // Wait for the view to be created... uint64 Start = LgiCurrentTime(); while (!Owner->Handle()) { LgiSleep(100); if (LgiCurrentTime() - Start > 5000) { LgiAssert(0); return -1; } } } Owner->PostEvent(M_LOAD, 0, (GMessage::Param) this); return 0; } }; class CompareView; class CmpZoomView : public GZoomView { CompareView *View; public: CmpZoomView(GZoomViewCallback *callback, CompareView *view); void OnMouseClick(GMouse &m); void OnMouseMove(GMouse &m); }; class CompareView : public GLayout { GZoomViewCallback *Callback; GEdit *AName, *BName, *CName; GAutoPtr A, B, C; CmpZoomView *AView, *BView, *CView; GArray Threads; GStatusBar *Status; GStatusPane *Pane[3]; bool DraggingView; GPointF DocPos; public: CompareView(GZoomViewCallback *callback, const char *FileA = NULL, const char *FileB = NULL) { Callback = callback; AName = BName = CName = NULL; DraggingView = false; SetPourLargest(true); AddView(Status = new GStatusBar); Status->AppendPane(Pane[0] = new GStatusPane); Status->AppendPane(Pane[1] = new GStatusPane); Status->AppendPane(Pane[2] = new GStatusPane); if (FileA) { GAutoString a(NewStr(FileA)); Threads.Add(new ThreadLoader(this, a, 0)); } if (FileB) { GAutoString a(NewStr(FileB)); Threads.Add(new ThreadLoader(this, a, 1)); } AddView(AName = new GEdit(IDC_A_NAME, 0, 0, 100, SysFont->GetHeight() + 8, FileA)); AddView(BName = new GEdit(IDC_B_NAME, 0, 0, 100, SysFont->GetHeight() + 8, FileB)); AddView(CName = new GEdit(IDC_C_NAME, 0, 0, 100, SysFont->GetHeight() + 8, NULL)); CName->Sunken(false); CName->Enabled(false); AddView(AView = new CmpZoomView(Callback, this)); AView->SetId(IDC_A_VIEW); AView->Name("AView"); AddView(BView = new CmpZoomView(Callback, this)); BView->SetId(IDC_B_VIEW); BView->Name("BView"); AddView(CView = new CmpZoomView(Callback, this)); CView->SetId(IDC_C_VIEW); CView->Name("CView"); #if 0 AView->SetSampleMode(SuperView::SampleAverage); BView->SetSampleMode(SuperView::SampleAverage); // CView->SetSampleMode(SuperView::SampleMax); #endif } ~CompareView() { Threads.DeleteObjects(); } void OnCreate() { AttachChildren(); } void OnPaint(GSurface *pDC) { pDC->Colour(LC_MED, 24); pDC->Rectangle(); } GMessage::Param OnEvent(GMessage *Msg) { switch (MsgCode(Msg)) { case M_LOAD: { ThreadLoader *t = (ThreadLoader*) MsgB(Msg); if (t) { if (t->Param == 0) { A = t->Img; AView->SetSurface(A, false); } else if (t->Param == 1) { B = t->Img; BView->SetSurface(B, false); } Threads.Delete(t); DeleteObj(t); } if (A && B) { GAutoPtr pDC = CreateDiff(this, A, B); if (pDC) { if (C.Reset(pDC.Release())) { CView->SetSurface(C, false); } } } OnPosChange(); break; } } return GLayout::OnEvent(Msg); } void OnPosChange() { GLayout::OnPosChange(); if (AName && BName && AView && BView && CView) { GRect c = GetClient(); GRegion rgn; rgn = c; if (Status) { Status->Pour(rgn); rgn.Subtract(&Status->GetPos()); c = rgn.Bound(); } int width = (c.X() - 10) / 3; GRect ar = c; ar.x2 = ar.x1 + width - 1; GRect cr = c; cr.x1 = ar.x2 + 6; cr.x2 = cr.x1 + width - 1; GRect br = c; br.x1 = br.x2 - width; Pane[0]->SetWidth(cr.x1); Pane[1]->SetWidth(br.x1 - cr.x1); Pane[2]->SetWidth(cr.X()); GRect name = ar; name.y2 = name.y1 + AName->Y() - 1; AName->SetPos(name); ar.y1 = name.y2 + 1; AView->SetPos(ar); name = br; name.y2 = name.y1 + BName->Y() - 1; BName->SetPos(name); br.y1 = name.y2 + 1; BView->SetPos(br); name = cr; name.y2 = name.y1 + CName->Y() - 1; CName->SetPos(name); cr.y1 = name.y2 + 1; CView->SetPos(cr); AView->Visible(true); BView->Visible(true); CView->Visible(true); } } void OnViewportChange() { if (CView && CName) { GZoomView::ViewportInfo i = CView->GetViewport(); char s[256]; int ch = sprintf_s(s, sizeof(s), "Scroll: %i,%i ", i.Sx, i.Sy); if (i.Zoom < 0) ch += sprintf_s(s+ch, sizeof(s)-ch, "Zoom: 1/%i", 1 - i.Zoom); else ch += sprintf_s(s+ch, sizeof(s)-ch, "Zoom: %ix", i.Zoom + 1); CName->Name(s); } } int OnNotify(GViewI *Ctrl, int Flags) { switch (Ctrl->GetId()) { case IDC_A_VIEW: { GZoomView::ViewportInfo i = AView->GetViewport(); BView->SetViewport(i); CView->SetViewport(i); OnViewportChange(); break; } case IDC_B_VIEW: { GZoomView::ViewportInfo i = BView->GetViewport(); AView->SetViewport(i); CView->SetViewport(i); OnViewportChange(); break; } case IDC_C_VIEW: { GZoomView::ViewportInfo i = CView->GetViewport(); AView->SetViewport(i); BView->SetViewport(i); OnViewportChange(); break; } } return GLayout::OnNotify(Ctrl, Flags); } int ZoomToFactor(int Zoom) { return Zoom < 0 ? 1 - Zoom : Zoom + 1; } template void PixToStr15(char *s, Px *p, GRgba64 *Diff) { sprintf(s, "%i,%i,%i (%x,%x,%x)", G5bitTo8bit(p->r), G5bitTo8bit(p->g), G5bitTo8bit(p->b), p->r, p->g, p->b); Diff->r = p->r; Diff->g = p->g; Diff->b = p->b; Diff->a = -1; } template void PixToStr16(char *s, Px *p, GRgba64 *Diff) { sprintf(s, "%i,%i,%i (%x,%x,%x)", G5bitTo8bit(p->r), G6bitTo8bit(p->g), G5bitTo8bit(p->b), p->r, p->g, p->b); Diff->r = p->r; Diff->g = p->g; Diff->b = p->b; Diff->a = -1; } template void PixToStrRgb(char *s, Px *p, GRgba64 *Diff) { sprintf(s, "%i,%i,%i (%x,%x,%x)", p->r, p->g, p->b, p->r, p->g, p->b); Diff->r = p->r; Diff->g = p->g; Diff->b = p->b; Diff->a = -1; } template void PixToStrRgba(char *s, Px *p, GRgba64 *Diff) { sprintf(s, "%i,%i,%i,%i (%x,%x,%x,%x)", p->r, p->g, p->b, p->a, p->r, p->g, p->b, p->a); Diff->r = p->r; Diff->g = p->g; Diff->b = p->b; Diff->a = p->a; } GAutoString DescribePixel(GSurface *pDC, GdcPt2 Pos, GRgba64 *Diff) { char s[256] = "No Data"; int ch = 0; switch (pDC->GetColourSpace()) { case CsIndex8: { COLOUR c = pDC->Get(Pos.x, Pos.y); ch = sprintf_s(s, sizeof(s), "%i (%.2x)", c, c); break; } #define DescribeCase(type, method) \ case Cs##type: \ { \ G##type *p = (G##type*)((*pDC)[Pos.y]); \ if (p) method(s, p + Pos.x, Diff); \ break; \ } DescribeCase(Rgb15, PixToStr15) DescribeCase(Bgr15, PixToStr15) DescribeCase(Rgb16, PixToStr16) DescribeCase(Bgr16, PixToStr16) DescribeCase(Rgb24, PixToStrRgb) DescribeCase(Bgr24, PixToStrRgb) DescribeCase(Rgbx32, PixToStrRgb) DescribeCase(Bgrx32, PixToStrRgb) DescribeCase(Xrgb32, PixToStrRgb) DescribeCase(Xbgr32, PixToStrRgb) DescribeCase(Rgb48, PixToStrRgb) DescribeCase(Bgr48, PixToStrRgb) DescribeCase(Rgba32, PixToStrRgba) DescribeCase(Bgra32, PixToStrRgba) DescribeCase(Argb32, PixToStrRgba) DescribeCase(Abgr32, PixToStrRgba) DescribeCase(Rgba64, PixToStrRgba) DescribeCase(Bgra64, PixToStrRgba) default: LgiAssert(0); break; } return GAutoString(NewStr(s)); } /* #undef DefOption #define DefOption(_type, _name, _opt, _def) \ _type _name() { GVariant v = _def; App->GetOptions()->GetValue(_opt, v); return v.CastInt32(); } \ void _name(_type i) { GVariant v; App->GetOptions()->SetValue(_opt, v = i); } DefOption(int, DisplayGrid, OPT_DisplayGrid, true); DefOption(int, GridSize, OPT_GridSize, 8); DefOption(int, DisplayTile, OPT_DisplayTile, true); DefOption(int, TileType, OPT_TileType, 0); DefOption(int, TileX, OPT_TileX, 16); DefOption(int, TileY, OPT_TileY, 16); void UserKey(ImgKey &k) { } void UserMouseEnter(ImgMouse &m) { } void UserMouseExit(ImgMouse &m) { } */ void UserMouseClick(GMouse &m) { GZoomView *zv = dynamic_cast(m.Target); if (!zv) { LgiAssert(0); return; } zv->Capture(DraggingView = m.Down()); if (m.Down()) { GZoomView *zv = dynamic_cast(m.Target); if (!zv->Convert(DocPos, m.x, m.y)) LgiAssert(0); zv->Focus(true); } } void UserMouseMove(GMouse &m) { if (DraggingView) { GZoomView::ViewportInfo vp = AView->GetViewport(); int Factor = ZoomToFactor(vp.Zoom); if (vp.Zoom < 0) { // scaling down vp.Sx = (int) (DocPos.x - (Factor * m.x)); vp.Sy = (int) (DocPos.y - (Factor * m.y)); } else if (vp.Zoom > 0) { // scaling up vp.Sx = (int) (DocPos.x - (m.x / Factor)); vp.Sy = (int) (DocPos.y - (m.y / Factor)); } else { // 1:1 vp.Sx = (int) (DocPos.x - m.x); vp.Sy = (int) (DocPos.y - m.y); } AView->SetViewport(vp); BView->SetViewport(vp); CView->SetViewport(vp); OnViewportChange(); } if (AView && BView) { GSurface *a = AView->GetSurface(); GSurface *b = BView->GetSurface(); if (a && b) { GRgba64 ap, bp; ZeroObj(ap); ZeroObj(bp); GAutoString Apix = DescribePixel(a, GdcPt2(m.x, m.y), &ap); Pane[0]->Name(Apix); GAutoString Bpix = DescribePixel(b, GdcPt2(m.x, m.y), &bp); Pane[2]->Name(Bpix); int Channels = GColourSpaceChannels(a->GetColourSpace()); int TileX = Callback->TileX(); int TileY = Callback->TileY(); int Tx = m.x / TileX; int Ty = m.y / TileY; int Tile = Ty * TileX + Tx; char s[256]; int diffr = bp.r - ap.r; int diffg = bp.g - ap.g; int diffb = bp.b - ap.b; int diffa = bp.a - ap.a; #define PercentDiff(c) \ diff##c, (ap.c ? (double)abs(diff##c) * 100 / ap.c : (diff##c ? 100.0 : 0.0)) GZoomView *zv = dynamic_cast(m.Target); GPointF Doc; zv->Convert(Doc, m.x, m.y); int ch = sprintf_s(s, sizeof(s), "Mouse: %.1f, %.1f Tile: %i (%i, %i) Diff: %i(%.1f%%),%i(%.1f%%),%i(%.1f%%)", Doc.x, Doc.y, Tile, Tx, Ty, PercentDiff(r), PercentDiff(g), PercentDiff(b)); if (Channels > 3) ch += sprintf_s(s+ch, sizeof(s)-ch, ",%i(%.1f%%)", PercentDiff(b)); Pane[1]->Name(s); } } } }; CmpZoomView::CmpZoomView(GZoomViewCallback *callback, CompareView *view) : GZoomView(callback) { View = view; } void CmpZoomView::OnMouseClick(GMouse &m) { LgiAssert(m.Target == this); GZoomView::OnMouseClick(m); View->UserMouseClick(m); } void CmpZoomView::OnMouseMove(GMouse &m) { LgiAssert(m.Target == this); GZoomView::OnMouseMove(m); View->UserMouseMove(m); } struct CompareThread : public LThread { LList *lst; bool loop; CompareThread(LList *l) : LThread("CompareThread") { lst = l; loop = true; Run(); } ~CompareThread() { loop = false; while (!IsExited()) LgiSleep(1); } int Main() { List items; lst->GetAll(items); List::I it = items.begin(); while (loop) { LListItem *i = *it; if (i) { char *left = i->GetText(0); char *right = i->GetText(1); GAutoPtr left_img(LoadDC(left)); GAutoPtr right_img(LoadDC(right)); if (left_img && right_img) { if (left_img->X() == right_img->X() && left_img->Y() == right_img->Y() && left_img->GetColourSpace() == right_img->GetColourSpace()) { bool diff = false; int bytes = (left_img->X() * left_img->GetBits()) / 8; for (int y=0; yY(); y++) { - uint8 *left_scan = (*left_img)[y]; - uint8 *right_scan = (*right_img)[y]; + uint8_t *left_scan = (*left_img)[y]; + uint8_t *right_scan = (*right_img)[y]; if (memcmp(left_scan, right_scan, bytes)) { diff = true; break; } } i->SetText(diff ? "Different" : "Same", 2); } else { i->SetText("SizeDiff", 2); } } else { i->SetText("Failed", 2); } i->Update(); } else break; it++; } return 0; } }; struct ImageCompareDlgPriv : public GZoomViewCallback { GCombo *l, *r; LList *lst; GTabView *tabs; GAutoPtr Thread; ImageCompareDlgPriv() { l = r = NULL; tabs = NULL; } void DrawBackground(GZoomView *View, GSurface *Dst, GdcPt2 Offset, GRect *Where) { Dst->Colour(LC_WORKSPACE, 24); Dst->Rectangle(Where); } void DrawForeground(GZoomView *View, GSurface *Dst, GdcPt2 Offset, GRect *Where) { } void SetStatusText(const char *Msg, int Pane = 0) { } }; ImageCompareDlg::ImageCompareDlg(GView *p, const char *OutPath) { d = new ImageCompareDlgPriv(); SetParent(p); GRect r(0, 0, 1200, 900); SetPos(r); MoveToCenter(); Name("Image Compare"); if (!Attach(0)) { delete this; return; } GFile::Path ResFile(__FILE__); ResFile--; ResFile += "ImageComparison.lr8"; if (!ResFile.Exists()) { GAutoString r(LgiFindFile("ImageComparison.lr8")); if (r) ResFile = r.Get(); } LgiAssert(ResFile.GetFull()); if (ResFile.GetFull()) { AddView(d->tabs = new GTabView(IDC_TAB_VIEW)); d->tabs->SetPourLargest(true); GTabPage *First = d->tabs->Append("Select"); LgiResources *Res = LgiGetResObj(false, ResFile.GetFull()); LgiAssert(Res); if (Res && Res->LoadDialog(IDD_COMPARE, First)) { MoveToCenter(); GButton *b; if (GetViewById(IDC_COMPARE, b)) { b->Default(true); } if (GetViewById(IDC_LEFT, d->l) && GetViewById(IDC_RIGHT, d->r) && GetViewById(IDC_LIST, d->lst)) { GDirectory dir; for (bool b = dir.First(OutPath); b; b = dir.Next()) { if (dir.IsDir()) { char p[MAX_PATH]; if (dir.Path(p, sizeof(p))) { d->l->Insert(p); d->r->Insert(p); } } } d->l->Value(0); d->r->Value(1); } } } AttachChildren(); Visible(true); } ImageCompareDlg::~ImageCompareDlg() { DeleteObj(d); } int ImageCompareDlg::OnNotify(GViewI *Ctrl, int Flags) { switch (Ctrl->GetId()) { case IDC_LIST: { if (Flags == GNotifyItem_DoubleClick) { LListItem *s = d->lst->GetSelected(); if (s) { char *left = s->GetText(0); char *right = s->GetText(1); #if 0 char p[MAX_PATH]; LgiGetSystemPath(LSP_APP_INSTALL, p, sizeof(p)); LgiMakePath(p, sizeof(p), p, "../../../../i.Mage/trunk/Win32Debug/image.exe"); if (FileExists(p)) { char args[MAX_PATH]; sprintf_s(args, sizeof(args), "\"%s\" \"%s\"", left, right); LgiExecute(p, args); } #else char *Leaf = strrchr(left, DIR_CHAR); int Len = d->tabs->GetTabs(); GTabPage *t = d->tabs->Append(Leaf ? Leaf + 1 : left); if (t) { t->HasButton(true); t->SetId(IDC_TAB_PAGE); CompareView *cv = new CompareView(d, left, right); t->Append(cv); d->tabs->Value(Len); } #endif } } break; } case IDCANCEL: { Quit(); break; } case IDC_COMPARE: { LHashTbl, char*> Left; GDirectory LDir, RDir; char p[MAX_PATH]; for (bool b=LDir.First(d->l->Name()); b; b=LDir.Next()) { if (LDir.IsDir()) continue; LDir.Path(p, sizeof(p)); Left.Add(LDir.GetName(), NewStr(p)); } for (bool b=RDir.First(d->r->Name()); b; b=RDir.Next()) { char *LeftFile = Left.Find(RDir.GetName()); if (LeftFile) { RDir.Path(p, sizeof(p)); LListItem *l = new LListItem; l->SetText(LeftFile, 0); l->SetText(p, 1); l->SetText("Processing...", 2); d->lst->Insert(l); } } Left.DeleteArrays(); d->lst->ResizeColumnsToContent(); d->Thread.Reset(new CompareThread(d->lst)); break; } case IDC_TAB_PAGE: { if (Flags == GNotifyTabPage_ButtonClick) { GTabPage *p = dynamic_cast(Ctrl); LgiAssert(p); if (p) { GTabView *v = p->GetTabControl(); LgiAssert(v); if (v) { v->Delete(p); } } } break; } } return 0; } diff --git a/src/common/Lgi/GSubProcess.cpp b/src/common/Lgi/GSubProcess.cpp --- a/src/common/Lgi/GSubProcess.cpp +++ b/src/common/Lgi/GSubProcess.cpp @@ -1,1023 +1,1023 @@ /** \file \brief Sub-process wrapper. This class runs one or more sub-processes chained together by pipes. Example: GSubProcess p1("ls", "-l"); GSubProcess p2("grep", "string"); p1.Connect(&p2); p1.Start(true, false); int r; char Buf[256]; while ((r = p1.Read(Buf, sizeof(Buf))) > 0) { // So something with 'Buf' } */ #if defined(MAC) || defined(POSIX) #define _GNU_SOURCE #include #include #include #include #endif #ifdef BEOS #include #endif #include "Lgi.h" #include "GSubProcess.h" #include "GToken.h" #define DEBUG_SUBPROCESS 0 #if defined(WIN32) #define NULL_PIPE NULL #define ClosePipe CloseHandle #else #define NULL_PIPE -1 #define ClosePipe close #define INVALID_PID -1 #endif GSubProcess::Pipe::Pipe() { Read = Write = NULL_PIPE; } bool GSubProcess::Pipe::Create ( #ifdef WIN32 LPSECURITY_ATTRIBUTES pAttr #else void *UnusedParam #endif ) { #if defined(WIN32) return CreatePipe(&Read, &Write, pAttr, 0) != 0; #else return pipe(Handles) != NULL_PIPE; #endif } void GSubProcess::Pipe::Close() { if (Read != NULL_PIPE) { ClosePipe(Read); Read = NULL_PIPE; } if (Write != NULL_PIPE) { ClosePipe(Write); Write = NULL_PIPE; } } GSubProcess::GSubProcess(const char *exe, const char *args) { #if defined(POSIX) ChildPid = INVALID_PID; ExitValue = -1; #elif defined(WIN32) ChildPid = NULL; ChildHnd = NULL; ExitValue = 0; #endif NewGroup = true; ErrorCode = 0; Parent = Child = NULL; Exe = exe; Args.Add(Exe); EnvironmentChanged = false; ExternIn = NULL_PIPE; ExternOut = NULL_PIPE; #if DEBUG_SUBPROCESS LgiTrace("%s:%i - %p::GSubProcess('%s','%s')\n", _FL, this, exe, args); #endif char *s; while ((s = LgiTokStr(args))) { Args.Add(s); } } GSubProcess::~GSubProcess() { #if defined(POSIX) Io.Close(); #endif if (Child) { LgiAssert(Child->Parent == this); Child->Parent = NULL; } if (Parent) { LgiAssert(Parent->Child == this); Parent->Child = NULL; } } #ifndef WINDOWS extern char **environ; #endif GSubProcess::Variable *GSubProcess::GetEnvVar(const char *Var, bool Create) { if (Environment.Length() == 0) { // Read all variables in #ifdef WINDOWS LPWCH e = GetEnvironmentStringsW(); if (e) { char16 *s = e; while (*s) { char16 *eq = StrchrW(s, '='); if (!eq) break; ptrdiff_t NameChars = eq - s; if (NameChars > 0) { Variable &v = Environment.New(); v.Var.SetW(s, eq - s); eq++; v.Val.SetW(eq); } eq += StrlenW(eq); s = eq + 1; } FreeEnvironmentStringsW(e); } #else for (int i=0; environ[i]; i++) { auto p = GString(environ[i]).Split("=", 1); if (p.Length() == 2) { Variable &v = Environment.New(); v.Var = p[0]; v.Val = p[1]; } } #endif } for (unsigned i=0; iVal.Get() : NULL; } bool GSubProcess::SetEnvironment(const char *Var, const char *Value) { Variable *v = GetEnvVar(Var, true); if (!v) return false; bool IsPath = !_stricmp(Var, "PATH"); GStringPipe a; const char *s = Value; while (*s) { char *n = strchr(s, '%'); char *e = n ? strchr(n + 1, '%') : NULL; if (n && e) { a.Write(s, (int) (n-s)); n++; ptrdiff_t bytes = e - n; char Name[128]; if (bytes > sizeof(Name) - 1) bytes = sizeof(Name)-1; memcpy(Name, n, bytes); Name[bytes] = 0; const char *existing = GetEnvironment(Name); if (existing) { a.Write(existing, (int)strlen(existing)); } s = e + 1; } else { a.Write(s, (int)strlen(s)); break; } } v->Val = a.NewGStr(); if (IsPath) { // Remove missing paths from the list GToken t(v->Val, LGI_PATH_SEPARATOR); GStringPipe p; for (unsigned i=0; iVal = p.NewGStr(); } EnvironmentChanged = true; return true; } bool GSubProcess::GetValue(const char *Var, ::GVariant &Value) { switch (LgiStringToDomProp(Var)) { case StreamReadable: { #ifdef WINNATIVE char Buf[32] = ""; DWORD lpBytesRead = 0; BOOL b = PeekNamedPipe( ChildOutput.Read, Buf, sizeof(Buf), &lpBytesRead, NULL, NULL); Value = b && lpBytesRead > 0; break; #endif } /* case StreamWritable: { break; } */ default: return false; } return true; } void GSubProcess::SetStdin(OsFile Hnd) { ExternIn = Hnd; } void GSubProcess::SetStdout(OsFile Hnd) { ExternOut = Hnd; } void GSubProcess::Connect(GSubProcess *child) { Child = child; if (Child) { Child->Parent = this; } } bool GSubProcess::Start(bool ReadAccess, bool WriteAccess, bool MapStderrToStdout) { bool Status = false; #if USE_SIMPLE_FORK int in[2]; if (pipe(in) == -1) { printf("parent: Failed to create stdin pipe"); return false; } int out[2]; if (pipe(out) == -1) { printf("parent: Failed to create stdout pipe"); return false; } ChildPid = fork(); if (ChildPid == 0) { // We are in the child process. if (InitialFolder) { chdir(InitialFolder); } // Child shouldn't write to its stdin. if (close(in[1])) printf("%s:%i - close failed.\n", _FL); // Child shouldn't read from its stdout. if (close(out[0])) printf("%s:%i - close failed.\n", _FL); // Redirect stdin and stdout for the child process. if (dup2(in[0], fileno(stdin)) == -1) { printf("%s:%i - child[pre-exec]: Failed to redirect stdin for child\n", _FL); return false; } if (close(in[0])) printf("%s:%i - close failed.\n", _FL); if (dup2(out[1], fileno(stdout)) == -1) { printf("%s:%i - child[pre-exec]: Failed to redirect stdout for child\n", _FL); return false; } if (dup2(out[1], fileno(stderr)) == -1) { printf("%s:%i - child[pre-exec]: Failed to redirect stderr for child\n", _FL); return false; } close(out[1]); // Execute the child Args.Add(NULL); if (Environment.Length()) { GString::Array Vars; GArray Env; Vars.SetFixedLength(false); for (auto v : Environment) { GString &s = Vars.New(); s.Printf("%s=%s", v.Var.Get(), v.Val.Get()); Env.Add(s.Get()); } Env.Add(NULL); execve(Exe, &Args[0], Env.AddressOf()); } else { execvp(Exe, &Args[0]); } // Execution will pass to here if the 'Exe' can't run or doesn't exist // So by exiting with an error the parent process can handle it. exit(GSUBPROCESS_ERROR); } else { // We are in the parent process. if (ChildPid == -1) { printf("%s:%i - parent: Failed to create child", _FL); return false; } // Parent shouldn't read from child's stdin. if (close(in[0])) printf("%s:%i - close failed.\n", _FL); // Parent shouldn't write to child's stdout. if (close(out[1])) printf("%s:%i - close failed.\n", _FL); Io.Read = out[0]; Io.Write = in[1]; // printf("USE_SIMPLE_FORK success.\n"); return true; } #else #if DEBUG_SUBPROCESS LgiTrace("%s:%i - %p::Start(%i,%i,%i)\n", _FL, this, ReadAccess, WriteAccess, MapStderrToStdout); #endif // Find the end of the process list ::GArray p; for (GSubProcess *s=this; s; s=s->Child) { LgiAssert(!s->Child || s->Child->Parent == s); p.Add(s); } size_t Kids = p.Length() + 1; #ifdef WIN32 SECURITY_ATTRIBUTES Attr; Attr.nLength = sizeof(SECURITY_ATTRIBUTES); Attr.bInheritHandle = true; Attr.lpSecurityDescriptor = NULL; #else int Attr = 0; #endif #if defined(POSIX) ::GArray Pipes; Pipes.Length(Kids); Pipes[0].Create(&Attr); #if DEBUG_SUBPROCESS LgiTrace("%s:%i - *PARENT* pipe[%i].create %i,%i\n", _FL, 0, Pipes[0].Read, Pipes[0].Write); #endif Status = true; for (int i=1; iChildPid = fork(); if (sp->ChildPid == INVALID_PID) { LgiTrace("%s:%i - fork failed with %i", _FL, errno); exit(1); } else if (sp->ChildPid == 0) { if (InitialFolder) { chdir(InitialFolder); } // Close irrelevant pipes for (int j = 0; j < i-1; j++) { #if DEBUG_SUBPROCESS LgiTrace("%s:%i - *CHILD* pipe[%i].close %i,%i\n", _FL, j, Pipes[j].Read, Pipes[j].Write); #endif Pipes[j].Close(); } // Set up STDIN and STDOUT Pipe &in = Pipes[i-1]; Pipe &out = Pipes[i]; #if DEBUG_SUBPROCESS LgiTrace("%s:%i - *CHILD* %i) Child init %i->'%s'->%i\n", _FL, i, in.Read, sp->Exe.Get(), out.Write); #endif #if DEBUG_SUBPROCESS LgiTrace("%s:%i - *CHILD* Dupe %i->%i\n", _FL, in.Read, STDIN_FILENO); #endif Dupe(in.Read, STDIN_FILENO); #if DEBUG_SUBPROCESS LgiTrace("%s:%i - *CHILD* Close %i\n", _FL, in.Write); #endif close(in.Write); #if DEBUG_SUBPROCESS LgiTrace("%s:%i - *CHILD* Dupe %i->%i\n", _FL, out.Write, STDOUT_FILENO); #endif Dupe(out.Write, STDOUT_FILENO); #if DEBUG_SUBPROCESS LgiTrace("%s:%i - *CHILD* Dupe %i->%i\n", out.Write, STDERR_FILENO); #endif Dupe(out.Write, STDERR_FILENO); #if DEBUG_SUBPROCESS LgiTrace("%s:%i - *CHILD* Close %i\n", _FL, out.Read); #endif close(out.Read); fsync(STDOUT_FILENO); LgiSleep(100); // Execute the child sp->Args.Add(NULL); execvp(sp->Exe, &sp->Args[0]); LgiTrace("%s:%i - execvp('%s').\n", _FL, sp->Exe.Get()); for (int i=0; iArgs.Length(); i++) LgiTrace("%s:%i - Args[%i]='%s'\n", _FL, i, sp->Args[i]); Status = false; break; } } // Close irrelevant pipes for (int j = 1; j < Kids - 1; j++) { #if DEBUG_SUBPROCESS LgiTrace("%s:%i - *PARENT* pipe[%i].close %i,%i\n", _FL, j, Pipes[j].Read, Pipes[j].Write); #endif Pipes[j].Close(); } #if DEBUG_SUBPROCESS LgiTrace("%s:%i - *PARENT* pipe[0].close %i, pipe[%i].close %i\n", _FL, Pipes[0].Read, Pipes.Length()-1, Pipes.Last().Write); #endif close(Pipes[0].Read); close(Pipes.Last().Write); // Set the input and output pipes for this sub-process. if (WriteAccess) Io.Write = Pipes[0].Write; else { #if DEBUG_SUBPROCESS LgiTrace("%s:%i - *PARENT* pipe[0].close %i\n", _FL, Pipes[0].Write); #endif close(Pipes[0].Write); } if (ReadAccess) Io.Read = Pipes.Last().Read; else { #if DEBUG_SUBPROCESS LgiTrace("%s:%i - *PARENT* pipe[%i].close %i\n", _FL, Pipes.Length()-1, Pipes.Last().Read); #endif close(Pipes.Last().Read); } // LgiTrace("Final Handles %i, %i\n", Io.Read, Io.Write); #elif defined(WIN32) GAutoWString WExe; if (FileExists(Exe)) { WExe.Reset(Utf8ToWide(Exe)); } else { char *Ext = LgiGetExtension(Exe); bool HasExt = Ext && _stricmp(Ext, "exe") == 0; #if defined(WIN32) && !defined(PLATFORM_MINGW) GToken p; char *sPath = NULL; size_t sSize; errno_t err = _dupenv_s(&sPath, &sSize, "PATH"); if (err == 0) p.Parse(sPath, LGI_PATH_SEPARATOR); free(sPath); #else GToken p(getenv("PATH"), LGI_PATH_SEPARATOR); #endif for (unsigned i=0; i 0) { WArg[Ch++] = ' '; } if (strchr(a, ' ')) Ch += swprintf_s(WArg+Ch, CountOf(WArg)-Ch, L"\"%s\"", aw.Get()); else Ch += swprintf_s(WArg+Ch, CountOf(WArg)-Ch, L"%s", aw.Get()); } #if DEBUG_SUBPROCESS LgiTrace("%s:%i - Args='%S'\n", _FL, WArg); #endif bool HasExternIn = ExternIn != NULL_PIPE; #if DEBUG_SUBPROCESS LgiTrace("%s:%i - Oringinal handles, out=%p, in=%p, HasExternIn=%i\n", _FL, OldStdout, OldStdin, HasExternIn); #endif if (ChildOutput.Create(&Attr) && (HasExternIn || ChildInput.Create(&Attr))) { if (!SetHandleInformation(ChildOutput.Read, HANDLE_FLAG_INHERIT, 0)) LgiTrace("%s:%i - SetHandleInformation failed.\n", _FL); if (!HasExternIn && !SetHandleInformation(ChildInput.Write, HANDLE_FLAG_INHERIT, 0)) LgiTrace("%s:%i - SetHandleInformation failed.\n", _FL); #if DEBUG_SUBPROCESS LgiTrace("%s:%i - Output Pipe: rd=%p, wr=%p\n", _FL, ChildOutput.Read, ChildOutput.Write); if (!HasExternIn) LgiTrace("%s:%i - Input Pipe: rd=%p, wr=%p\n", _FL, ChildInput.Read, ChildInput.Write); #endif STARTUPINFOW Info; ZeroObj(Info); Info.cb = sizeof(Info); PROCESS_INFORMATION ProcInfo; ZeroObj(ProcInfo); Info.dwFlags = STARTF_USESTDHANDLES; Info.hStdOutput = ChildOutput.Write; Info.hStdInput = HasExternIn ? ExternIn : ChildInput.Read; if (MapStderrToStdout) Info.hStdError = ChildOutput.Write; GAutoWString WInitialFolder(Utf8ToWide(InitialFolder)); #if DEBUG_SUBPROCESS LgiTrace("%s:%i - WInitialFolder=%S, EnvironmentChanged=%i\n", _FL, WInitialFolder.Get(), EnvironmentChanged); #endif GAutoWString WEnv; if (EnvironmentChanged) { GMemQueue q(256); for (unsigned i=0; i 0) p.Write(Buf, Rd); else break; } return p.NewGStr(); } ssize_t GSubProcess::Read(void *Buf, ssize_t Size, int TimeoutMs) { #if defined(POSIX) bool DoRead = true; if (TimeoutMs) { OsSocket s = Io.Read; if (ValidSocket(s)) { struct timeval t = {TimeoutMs / 1000, (TimeoutMs % 1000) * 1000}; fd_set r; FD_ZERO(&r); FD_SET(s, &r); int v = select((int)s+1, &r, 0, 0, &t); if (v > 0 && FD_ISSET(s, &r)) { DoRead = true; } else { // printf("SubProc not readable..\n"); return 0; } } else LgiTrace("%s:%i - Invalid socket.\n", _FL); } return (int)read(Io.Read, Buf, Size); #else DWORD Rd = -1, Sz; if (!ReadFile(ChildOutput.Read, Buf, AssertCast(Sz, Size), &Rd, NULL)) return -1; return Rd; #endif } int GSubProcess::Peek() { #if defined(POSIX) int bytesAvailable = 0; int r = ioctl(Io.Read, FIONREAD, &bytesAvailable); return r ? -1 : bytesAvailable; #else DWORD Rd = 0, Avail = 0; char Buf[32]; if (PeekNamedPipe(ChildOutput.Read, Buf, sizeof(Buf), &Rd, &Avail, NULL)) return Rd; return 0; #endif } bool GSubProcess::Write(GString s) { auto Wr = Write(s.Get(), s.Length()); return Wr == s.Length(); } ssize_t GSubProcess::Write(const void *Buf, ssize_t Size, int Flags) { #if defined(POSIX) return (int)write(Io.Write, Buf, Size); #else DWORD Wr = -1, Sz; if (!WriteFile(ChildInput.Write, Buf, AssertCast(Sz, Size), &Wr, NULL)) return -1; return Wr; #endif } diff --git a/src/common/Text/GHtml.cpp b/src/common/Text/GHtml.cpp --- a/src/common/Text/GHtml.cpp +++ b/src/common/Text/GHtml.cpp @@ -1,9187 +1,9187 @@ #include #include #include #include #include "Lgi.h" #include "GHtml.h" #include "GHtmlPriv.h" #include "GToken.h" #include "GScrollBar.h" #include "GVariant.h" #include "GFindReplaceDlg.h" #include "GUnicode.h" #include "Emoji.h" #include "GClipBoard.h" #include "GButton.h" #include "GEdit.h" #include "GCombo.h" #include "GdcTools.h" #include "GDisplayString.h" #include "GPalette.h" #include "GPath.h" #include "GCssTools.h" #include "LgiRes.h" #include "INet.h" #define DEBUG_TABLE_LAYOUT 0 #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 LUIS_DEBUG 0 #define CRASH_TRACE 0 #ifdef MAC #define GHTML_USE_DOUBLE_BUFFER 0 #else #define GHTML_USE_DOUBLE_BUFFER 1 #endif #define GT_TRANSPARENT 0x00000000 #ifndef IDC_HAND #define IDC_HAND MAKEINTRESOURCE(32649) #endif #define M_JOBS_LOADED (M_USER+4000) #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 #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 == GCss::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 GHtmlPrivate { public: LHashTbl, GTag*> Loading; GHtmlStaticInst Inst; bool CursorVis; GRect CursorPos; GdcPt2 Content; bool WordSelectMode; bool LinkDoubleClick; GAutoString OnLoadAnchor; bool DecodeEmoji; GAutoString EmojiImg; int NextCtrlId; uint64 SetScrollTime; int DeferredLoads; bool IsParsing; bool IsLoaded; bool StyleDirty; // Find settings GAutoWString FindText; bool MatchCase; GHtmlPrivate() { 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]; #ifdef MAC LgiGetExeFile(EmojiPng, sizeof(EmojiPng)); LgiMakePath(EmojiPng, sizeof(EmojiPng), EmojiPng, "Contents/Resources/Emoji.png"); #else LGetSystemPath(LSP_APP_INSTALL, EmojiPng, sizeof(EmojiPng)); LgiMakePath(EmojiPng, sizeof(EmojiPng), EmojiPng, "resources/emoji.png"); #endif if (FileExists(EmojiPng)) { DecodeEmoji = true; EmojiImg.Reset(NewStr(EmojiPng)); } else DecodeEmoji = false; } ~GHtmlPrivate() { } }; }; ////////////////////////////////////////////////////////////////////// namespace Html1 { class InputButton : public GButton { GTag *Tag; public: InputButton(GTag *tag, int Id, const char *Label) : GButton(Id, 0, 0, -1, -1, Label) { Tag = tag; } void OnClick() { Tag->OnClick(); } }; class GFontCache { GHtml *Owner; List Fonts; public: GFontCache(GHtml *owner) { Owner = owner; } ~GFontCache() { Fonts.DeleteObjects(); } GFont *FontAt(int i) { return Fonts.ItemAt(i); } GFont *FindMatch(GFont *m) { for (GFont *f = Fonts.First(); f; f = Fonts.Next()) { if (*f == *m) { return f; } } return 0; } GFont *GetFont(GCss *Style) { if (!Style) return NULL; GFont *Default = Owner->GetFont(); GCss::StringsDef Face = Style->FontFamily(); if (Face.Length() < 1 || !ValidStr(Face[0])) { Face.Empty(); const char *DefFace = Default->Face(); LgiAssert(ValidStr(DefFace)); Face.Add(NewStr(DefFace)); } LgiAssert(ValidStr(Face[0])); GCss::Len Size = Style->FontSize(); GCss::FontWeightType Weight = Style->FontWeight(); bool IsBold = Weight == GCss::FontWeightBold || Weight == GCss::FontWeightBolder || Weight > GCss::FontWeight400; bool IsItalic = Style->FontStyle() == GCss::FontStyleItalic; bool IsUnderline = Style->TextDecoration() == GCss::TextDecorUnderline; if (Size.Type == GCss::LenInherit || Size.Type == GCss::LenNormal) { Size.Type = GCss::LenPt; Size.Value = (float)Default->PointSize(); } GFont *f = 0; if (Size.Type == GCss::LenPx) { int RequestPx = (int)Size.Value; // Look for cached fonts of the right size... for (f=Fonts.First(); f; f=Fonts.Next()) { if (f->Face() && _stricmp(f->Face(), Face[0]) == 0 && f->Bold() == IsBold && f->Italic() == IsItalic && f->Underline() == IsUnderline) { int Px = FontPxHeight(f); if (abs(Px - RequestPx) < 2) return f; } } } else if (Size.Type == GCss::LenPt) { double Pt = MAX(MinimumPointSize, Size.Value); for (f=Fonts.First(); f; f=Fonts.Next()) { if (!f->Face() || Face.Length() == 0) { LgiAssert(0); break; } auto FntSz = f->Size(); if (f->Face() && _stricmp(f->Face(), Face[0]) == 0 && FntSz.Type == GCss::LenPt && abs(FntSz.Value - Pt) < 0.001 && f->Bold() == IsBold && f->Italic() == IsItalic && f->Underline() == IsUnderline) { // Return cached font return f; } } } else if (Size.Type == GCss::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 = GCss::LenPt; Size.Value *= Default->PointSize() / 100.0; if (Size.Value < MinimumPointSize) Size.Value = MinimumPointSize; } else if (Size.Type == GCss::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 = GCss::LenPt; Size.Value *= Default->PointSize(); if (Size.Value < MinimumPointSize) Size.Value = MinimumPointSize; } else if (Size.Type == GCss::SizeXXSmall || Size.Type == GCss::SizeXSmall || Size.Type == GCss::SizeSmall || Size.Type == GCss::SizeMedium || Size.Type == GCss::SizeLarge || Size.Type == GCss::SizeXLarge || Size.Type == GCss::SizeXXLarge) { int Idx = Size.Type-GCss::SizeXXSmall; LgiAssert(Idx >= 0 && Idx < CountOf(GCss::FontSizeTable)); Size.Type = GCss::LenPt; Size.Value = Default->PointSize() * GCss::FontSizeTable[Idx]; if (Size.Value < MinimumPointSize) Size.Value = MinimumPointSize; } else if (Size.Type == GCss::SizeSmaller) { Size.Type = GCss::LenPt; Size.Value = (float)(Default->PointSize() - 1); } else if (Size.Type == GCss::SizeLarger) { Size.Type = GCss::LenPt; Size.Value = (float)(Default->PointSize() + 1); } else LgiAssert(!"Not impl."); if ((f = new GFont)) { char *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 (!f->Create((char*)0, 0)) { // Broken font... f->Face(Default->Face()); GFont *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.First(); } } } // Not already cached Fonts.Insert(f); if (!f->Face()) { LgiAssert(0); } return f; } return 0; } }; class GFlowRegion { 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 GFlowStack { int LeftAbs; int RightAbs; int TopAbs; }; GArray Stack; public: GHtml *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 GdcPt2 MAX; // Max dimensions int Inline; int InBody; GFlowRegion(GHtml *html, bool inbody) { Html = html; x1 = x2 = y1 = y2 = cx = my = 0; Inline = 0; InBody = inbody; } GFlowRegion(GHtml *html, GRect 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; } GFlowRegion(GFlowRegion &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; } GString ToString() { GString 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; } GFlowRegion &operator +=(GRect r) { x1 += r.x1; cx += r.x1; x2 -= r.x2; y1 += r.y1; y2 += r.y1; return *this; } GFlowRegion &operator -=(GRect r) { x1 -= r.x1; cx -= r.x1; x2 += r.x2; y1 += r.y2; y2 += r.y2; return *this; } void FinishLine(GCss::LengthType Align, bool Margin = false); void EndBlock(GCss::LengthType Align); void Insert(GFlowRect *Tr); GRect *LineBounds(); void Indent(GTag *Tag, GCss::Len Left, GCss::Len Top, GCss::Len Right, GCss::Len Bottom, bool IsMargin) { GFlowRegion This(*this); GFlowStack &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(GRect &Px, bool IsMargin) { GFlowRegion This(*this); GFlowStack &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(GRect &Px, bool IsMargin) { GFlowRegion This = *this; ssize_t len = Stack.Length(); if (len > 0) { GFlowStack &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 LgiAssert(!"Nothing to pop."); } void Outdent(GTag *Tag, GCss::Len Left, GCss::Len Top, GCss::Len Right, GCss::Len Bottom, bool IsMargin) { GFlowRegion This = *this; ssize_t len = Stack.Length(); if (len > 0) { GFlowStack &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 LgiAssert(!"Nothing to pop."); } int ResolveX(GCss::Len l, GTag *t, bool IsMargin) { GFont *f = t->GetFont(); switch (l.Type) { default: case GCss::LenInherit: return IsMargin ? 0 : X(); case GCss::LenPx: // return MIN((int)l.Value, X()); return (int)l.Value; case GCss::LenPt: return (int) (l.Value * LgiScreenDpi() / 72.0); case GCss::LenCm: return (int) (l.Value * LgiScreenDpi() / 2.54); case GCss::LenEm: { if (!f) { LgiAssert(!"No font?"); f = SysFont; } return (int)(l.Value * f->GetHeight()); } case GCss::LenEx: { if (!f) { LgiAssert(!"No font?"); f = SysFont; } return (int) (l.Value * f->GetHeight() / 2); // More haha, who uses 'ex' anyway? } case GCss::LenPercent: { int my_x = X(); int px = (int) (l.Value * my_x / 100.0); return px; } case GCss::LenAuto: { if (IsMargin) return 0; else return X(); break; } case GCss::SizeSmall: { return 1; // px } case GCss::SizeMedium: { return 2; // px } case GCss::SizeLarge: { return 3; // px } } return 0; } bool LimitX(int &x, GCss::Len Min, GCss::Len Max, GFont *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(GCss::Len l, GTag *t, bool IsMargin) { GFont *f = t->GetFont(); switch (l.Type) { case GCss::LenInherit: case GCss::LenAuto: case GCss::LenNormal: case GCss::LenPx: return (int)l.Value; case GCss::LenPt: return (int) (l.Value * LgiScreenDpi() / 72.0); case GCss::LenCm: return (int) (l.Value * LgiScreenDpi() / 2.54); case GCss::LenEm: { if (!f) { f = SysFont; LgiAssert(!"No font"); } return (int) (l.Value * f->GetHeight()); } case GCss::LenEx: { if (!f) { f = SysFont; LgiAssert(!"No font"); } return (int) (l.Value * f->GetHeight() / 2); // More haha, who uses 'ex' anyway? } case GCss::LenPercent: { // Walk up tree of tags to find an absolute size... GCss::Len Ab; for (GTag *p = ToTag(t->Parent); p; p = ToTag(p->Parent)) { auto h = p->Height(); if (h.IsValid() && !h.IsDynamic()) { Ab = h; break; } } if (!Ab.IsValid()) { LgiAssert(Html != NULL); Ab.Type = GCss::LenPx; Ab.Value = Html->Y(); } GCss::Len m = Ab * l; return (int)m.ToPx(0, f);; } case GCss::SizeSmall: { return 1; // px } case GCss::SizeMedium: { return 2; // px } case GCss::SizeLarge: { return 3; // px } case GCss::AlignLeft: case GCss::AlignRight: case GCss::AlignCenter: case GCss::AlignJustify: case GCss::VerticalBaseline: case GCss::VerticalSub: case GCss::VerticalSuper: case GCss::VerticalTop: case GCss::VerticalTextTop: case GCss::VerticalMiddle: case GCss::VerticalBottom: case GCss::VerticalTextBottom: { // Meaningless in this context break; } default: { LgiAssert(!"Not supported."); break; } } return 0; } bool LimitY(int &y, GCss::Len Min, GCss::Len Max, GFont *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; } GRect ResolveMargin(GCss *Src, GTag *Tag) { GRect 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; } GRect ResolveBorder(GCss *Src, GTag *Tag) { GRect 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; } GRect ResolvePadding(GCss *Src, GTag *Tag) { GRect 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; } GLength::GLength() { d = 0; PrevAbs = 0; u = GCss::LenInherit; } GLength::GLength(char *s) { Set(s); } bool GLength::IsValid() { return u != GCss::LenInherit; } bool GLength::IsDynamic() { return u == GCss::LenPercent || d == 0.0; } GLength::operator float () { return d; } GLength &GLength::operator =(float val) { d = val; u = GCss::LenPx; return *this; } GCss::LengthType GLength::GetUnits() { return u; } void GLength::Set(char *s) { if (ValidStr(s)) { char Units[256] = ""; if (ParseDistance(s, d, Units)) { if (Units[0]) { if (strchr(Units, '%')) { u = GCss::LenPercent; } else if (stristr(Units, "pt")) { u = GCss::LenPt; } else if (stristr(Units, "em")) { u = GCss::LenEm; } else if (stristr(Units, "ex")) { u = GCss::LenEx; } else { u = GCss::LenPx; } } else { u = GCss::LenPx; } } } } float GLength::Get(GFlowRegion *Flow, GFont *Font, bool Lock) { switch (u) { default: break; case GCss::LenEm: { return PrevAbs = d * (Font ? Font->GetHeight() : 14); break; } case GCss::LenEx: { return PrevAbs = (Font ? Font->GetHeight() * d : 14) / 2; break; } case GCss::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); } GLine::GLine() { LineStyle = -1; LineReset = 0x80000000; } GLine::~GLine() { } GLine &GLine::operator =(int i) { d = (float)i; return *this; } void GLine::Set(char *s) { GToken t(s, " \t"); LineReset = 0x80000000; LineStyle = -1; char *Style = 0; for (unsigned i=0; iColourMap.Find(c) ) { GHtmlParser::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); } GHtmlParser::ParseColour(Buf, Colour); } else if (IsDigit(*c)) { GLength::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; } } } } ////////////////////////////////////////////////////////////////////// GRect GTag::GetRect(bool Client) { GRect r(Pos.x, Pos.y, Pos.x + Size.x - 1, Pos.y + Size.y - 1); if (!Client) { for (GTag *p = ToTag(Parent); p; p=ToTag(p->Parent)) { r.Offset(p->Pos.x, p->Pos.y); } } return r; } GCss::LengthType GTag::GetAlign(bool x) { for (GTag *t = this; t; t = ToTag(t->Parent)) { GCss::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 GFlowRegion::EndBlock(GCss::LengthType Align) { if (cx > x1) { FinishLine(Align); } } void GFlowRegion::FinishLine(GCss::LengthType Align, bool Margin) { if (Align != GCss::AlignLeft) { int Used = 0; for (auto l : Line) Used += l->X(); int Total = x2 - x1 + 1; if (Used < Total) { int Offset = 0; if (Align == GCss::AlignCenter) Offset = (Total - Used) / 2; else if (Align == GCss::AlignRight) Offset = Total - Used; if (Offset) for (auto l : Line) { if (l->Tag->Display() != GCss::DispInlineBlock) l->Offset(Offset, 0); } } } 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(); } GRect *GFlowRegion::LineBounds() { GFlowRect *Prev = Line.First(); GFlowRect *r=Prev; if (r) { GRect b; b = *r; int Ox = r->Tag->AbsX(); int Oy = r->Tag->AbsY(); b.Offset(Ox, Oy); // int Ox = 0, Oy = 0; while ((r = Line.Next())) { GRect 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 GRect Rgn; Rgn = b; return &Rgn; } return 0; } void GFlowRegion::Insert(GFlowRect *Tr) { if (Tr) Line.Insert(Tr); } ////////////////////////////////////////////////////////////////////// GTag::GTag(GHtml *h, GHtmlElement *p) : GHtmlElement(p), Attr(8) { Ctrl = 0; CtrlType = CtrlNone; TipId = 0; Display(DispInline); Html = h; ImageResized = false; Cursor = -1; Selection = -1; Font = 0; LineHeightCache = -1; HtmlId = NULL; // TableBorder = 0; Cell = NULL; TagId = CONTENT; Info = 0; Pos.x = Pos.y = 0; #ifdef _DEBUG Debug = false; #endif } GTag::~GTag() { if (Html->Cursor == this) { Html->Cursor = 0; } if (Html->Selection == this) { Html->Selection = 0; } DeleteObj(Ctrl); Attr.DeleteArrays(); DeleteObj(Cell); } void GTag::OnChange(PropType Prop) { } bool GTag::OnClick() { if (!Html->Environment) return false; const char *OnClick = NULL; if (Get("onclick", OnClick)) { Html->Environment->OnExecuteScript(Html, (char*)OnClick); } else { OnNotify(0); } return true; } void GTag::Set(const char *attr, const char *val) { char *existing = Attr.Find(attr); if (existing) DeleteArray(existing); if (val) Attr.Add(attr, NewStr(val)); } bool GTag::GetVariant(const char *Name, GVariant &Value, char *Array) { GDomProperty Fld = LgiStringToDomProp(Name); switch (Fld) { case ObjStyle: // Type: GCssStyle { 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 GTag::SetVariant(const char *Name, GVariant &Value, char *Array) { GDomProperty Fld = LgiStringToDomProp(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) { GAutoWString 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) { GTag *t = new GTag(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 GTag::GetTextStart() { if (PreText()) { GFlowRect *t = TextPos[1]; if (t) return t->Text - Text(); } else { GFlowRect *t = TextPos[0]; if (t) { LgiAssert(t->Text >= Text() && t->Text <= Text()+2); return t->Text - Text(); } } return 0; } static bool TextToStream(GStream &Out, char16 *Text) { if (!Text) return true; - uint8 Buf[256]; - uint8 *s = Buf; + 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 GTag::CreateSource(GStringPipe &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()) { GCss *Css = this; GCss Tmp; #define DelProp(p) \ if (Css == this) { Tmp = *Css; Css = &Tmp; } \ Css->DeleteProp(p); // Clean out any default CSS properties where we can... GHtmlElemInfo *i = GHtmlStatic::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: { GCss::ColorDef Blue(GCss::ColorRgb, Rgb32(0, 0, 255)); if (Props.Find(PropColor) && Color() == Blue) DelProp(PropColor); if (Props.Find(PropTextDecoration) && TextDecoration() == GCss::TextDecorUnderline) DelProp(PropTextDecoration) break; } case TAG_BODY: { GCss::Len FivePx(GCss::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() == GCss::FontWeightBold) DelProp(PropFontWeight); break; } case TAG_U: { if (Props.Find(PropTextDecoration) && TextDecoration() == GCss::TextDecorUnderline) DelProp(PropTextDecoration); break; } case TAG_I: { if (Props.Find(PropFontStyle) && FontStyle() == GCss::FontStyleItalic) DelProp(PropFontStyle); break; } } } // Convert CSS props to a string and emit them... GAutoString s = Css->ToString(); if (ValidStr(s)) { // Clean off any trailing whitespace... char *e = s ? s + strlen(s) : NULL; while (e && strchr(WhiteSpace, e[-1])) *--e = 0; // Print them to the tags attributes... p.Print(" style=\"%s\"", s.Get()); } } } if (Children.Length() || TagId == TAG_STYLE) // { if (Tag) { p.Write((char*)">", 1); TextToStream(p, Text()); } bool Last = IsBlock(); for (unsigned i=0; iCreateSource(p, Parent ? Depth+1 : 0, Last); Last = c->IsBlock(); } if (Tag) { if (IsBlock()) { if (Children.Length()) p.Print("\n%s", Tabs); } p.Print("", Tag.Get()); } } else if (Tag) { if (Text()) { p.Write((char*)">", 1); TextToStream(p, Text()); p.Print("", Tag.Get()); } else { p.Print("/>\n"); } } else { TextToStream(p, Text()); } DeleteArray(Tabs); return true; } void GTag::SetTag(const char *NewTag) { Tag.Reset(NewStr(NewTag)); if (NewTag) { Info = Html->GetTagInfo(Tag); if (Info) { TagId = Info->Id; Display(Info->Flags & GHtmlElemInfo::TI_BLOCK ? GCss::DispBlock : GCss::DispInline); } } else { Info = NULL; TagId = CONTENT; } SetStyle(); } GColour GTag::_Colour(bool f) { for (GTag *t = this; t; t = ToTag(t->Parent)) { ColorDef c = f ? t->Color() : t->BackgroundColor(); if (c.Type != ColorInherit) { return GColour(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 GColour(); } void GTag::CopyClipboard(GMemQueue &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; if (Min >= 0 && Max >= 0) { Off = Min; Chars = Max - Min; } else if (Min >= 0) { Off = Min; Chars = StrlenW(Text()) - Min; InSelection = true; } else if (Max >= 0) { Off = 0; Chars = Max; InSelection = false; } else if (InSelection) { Off = 0; Chars = StrlenW(Text()); } if (Off >= 0 && Chars > 0) { p.Write((uchar*) (Text() + Off), Chars * sizeof(char16)); } if (InSelection) { switch (TagId) { default: break; case TAG_BR: { char16 NL[] = {'\n', 0}; p.Write((uchar*) NL, sizeof(char16)); break; } case TAG_P: { char16 NL[] = {'\n', '\n', 0}; p.Write((uchar*) NL, sizeof(char16) * 2); break; } } } for (unsigned i=0; iCopyClipboard(p, InSelection); } } static char* _DumpColour(GCss::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 == GCss::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 GTag::_Dump(GStringPipe &Buf, int Depth) { GString 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); } } GAutoWString GTag::DumpW() { GStringPipe Buf; // Buf.Print("Html pos=%s\n", Html?Html->GetPos().GetStr():0); _Dump(Buf, 0); GAutoString a(Buf.NewStr()); GAutoWString w(Utf8ToWide(a)); return w; } GAutoString GTag::DescribeElement() { GStringPipe s(256); s.Print("%s", Tag ? Tag.Get() : "CONTENT"); if (HtmlId) s.Print("#%s", HtmlId); for (unsigned i=0; iDefFont(); } return f; } GFont *GTag::GetFont() { if (!Font) { if (PropAddress(PropFontFamily) != 0 || FontSize().Type != LenInherit || FontStyle() != FontStyleInherit || FontVariant() != FontVariantInherit || FontWeight() != FontWeightInherit || TextDecoration() != TextDecorInherit) { GCss c; GCss::PropMap Map; Map.Add(PropFontFamily, new GCss::PropArray); Map.Add(PropFontSize, new GCss::PropArray); Map.Add(PropFontStyle, new GCss::PropArray); Map.Add(PropFontVariant, new GCss::PropArray); Map.Add(PropFontWeight, new GCss::PropArray); Map.Add(PropTextDecoration, new GCss::PropArray); for (GTag *t = this; t; t = ToTag(t->Parent)) { if (!c.InheritCollect(*t, Map)) break; } c.InheritResolve(Map); Map.DeleteObjects(); if ((Font = Html->FontCache->GetFont(&c))) return Font; } else { GTag *t = this; while (!t->Font && t->Parent) { t = ToTag(t->Parent); } if (t->Font) return t->Font; } Font = Html->DefFont(); } return Font; } GTag *GTag::PrevTag() { if (Parent) { ssize_t i = Parent->Children.IndexOf(this); if (i >= 0) { return ToTag(Parent->Children[i - 1]); } } return 0; } void GTag::Invalidate() { GRect p = GetRect(); for (GTag *t=ToTag(Parent); t; t=ToTag(t->Parent)) { p.Offset(t->Pos.x, t->Pos.y); } Html->Invalidate(&p); } GTag *GTag::IsAnchor(GAutoString *Uri) { GTag *a = 0; for (GTag *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)) { GAutoWString w(CleanText(u, strlen(u), "utf-8")); if (w) { Uri->Reset(WideToUtf8(w)); } } } return a; } bool GTag::OnMouseClick(GMouse &m) { bool Processed = false; if (m.IsContextMenu()) { GAutoString Uri; GTag *a = IsAnchor(&Uri); if (a && ValidStr(Uri)) { GSubMenu RClick; #define IDM_COPY_LINK 100 if (Html->GetMouse(m, true)) { int Id = 0; RClick.AppendItem(LgiLoadString(L_COPY_LINK_LOCATION, "&Copy Link Location"), IDM_COPY_LINK, Uri != 0); if (Html->GetEnv()) Html->GetEnv()->AppendItems(&RClick); switch (Id = RClick.Float(Html, m.x, m.y)) { case IDM_COPY_LINK: { GClipBoard Clip(Html); Clip.Text(Uri); 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()) { GAutoString Style = ToString(); GStringPipe 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)) { GStringPipe 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()); } GAutoString Txt(Tmp.NewStr()); p.Print("%s", Txt.Get()); GDisplayString Ds(SysFont, 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); } GAutoString a(p.NewStr()); LgiMsg( Html, "%s", Html->GetClass(), MB_OK, a.Get()); } else #endif { GAutoString 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(); } } } } return Processed; } GTag *GTag::GetBlockParent(ssize_t *Idx) { if (IsBlock()) { if (Idx) *Idx = 0; return this; } for (GTag *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; } GTag *GTag::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; } GTag *GTag::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(GRect *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 GTag::NearestChar(GFlowRect *Tr, int x, int y) { GFont *f = GetFont(); if (f) { GDisplayString 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 GTag::GetTagByPos(GTagHit &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) { GRect 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 GTag::OnNotify(int f) { if (!Ctrl || !Html->InThread()) return 0; switch (CtrlType) { case CtrlSubmit: { GTag *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 GTag::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) { GStringPipe 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); } } GTag *GTag::FindCtrlId(int Id) { if (Ctrl && Ctrl->GetId() == Id) return this; for (unsigned i=0; iFindCtrlId(Id); if (f) return f; } return NULL; } void GTag::Find(int TagType, GArray &Out) { if (TagId == TagType) { Out.Add(this); } for (unsigned i=0; iFind(TagType, Out); } } void GTag::SetImage(const char *Uri, GSurface *Img) { if (Img) { if (TagId != TAG_IMG) { ImageDef *Def = (ImageDef*)GCss::Props.Find(PropBackgroundImage); if (Def) { Def->Type = ImageOwn; DeleteObj(Def->Img); Def->Img = Img; } } else { Image.Reset(Img); GRect r = XSubRect(); if (r.Valid()) { GAutoPtr t(new GMemDC(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 GTag::LoadImage(const char *Uri) { #if DOCUMENT_LOAD_IMAGES if (!Html->Environment) return; GUri u(Uri); bool LdImg = Html->GetLoadImages(); bool IsRemote = u.Protocol && ( !_stricmp(u.Protocol, "http") || !_stricmp(u.Protocol, "https") || !_stricmp(u.Protocol, "ftp") ); if (IsRemote && !LdImg) { Html->NeedsCapability("RemoteContent"); return; } GDocumentEnv::LoadJob *j = Html->Environment->NewJob(); if (j) { LgiAssert(Html != NULL); j->Uri.Reset(NewStr(Uri)); j->Env = Html->Environment; j->UserData = this; j->UserUid = Html->GetDocumentUid(); GDocumentEnv::LoadType Result = Html->Environment->GetContent(j); if (Result == GDocumentEnv::LoadImmediate) { SetImage(Uri, j->pDC.Release()); } else if (Result == GDocumentEnv::LoadDeferred) { Html->d->DeferredLoads++; } DeleteObj(j); } #endif } void GTag::LoadImages() { const char *Uri = 0; if (Html->Environment && TagId == TAG_IMG && !Image && Get("src", Uri)) { LoadImage(Uri); } for (unsigned i=0; iLoadImages(); } } void GTag::ImageLoaded(char *uri, GSurface *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 GMemDC(Img)); } Used++; } } for (unsigned i=0; iImageLoaded(uri, Img, Used); } } struct GTagElementCallback : public GCss::ElementCallback { const char *Val; const char *GetElement(GTag *obj) { return obj->Tag; } const char *GetAttr(GTag *obj, const char *Attr) { if (obj->Get(Attr, Val)) return Val; return NULL; } bool GetClasses(GString::Array &Classes, GTag *obj) { Classes = obj->Class; return Classes.Length() > 0; } GTag *GetParent(GTag *obj) { return ToTag(obj->Parent); } GArray GetChildren(GTag *obj) { GArray c; for (unsigned i=0; iChildren.Length(); i++) c.Add(ToTag(obj->Children[i])); return c; } }; void GTag::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 GTag::Restyle() { // Use the matching built into the GCss Store. GCss::SelArray Styles; GTagElementCallback 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) { GAutoString Style = ToString(); LgiTrace(">>>> %s <<<<:\n%s\n\n", Tag.Get(), Style.Get()); } #endif } void GTag::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 (GHtmlParser::ParseColour(s, Def)) { Color(Def); } } if (Get("Background", s) || Get("bgcolor", s)) { ColorDef Def; if (GHtmlParser::ParseColour(s, Def)) { BackgroundColor(Def); } else { GCss::ImageDef Img; Img.Type = ImageUri; Img.Uri = s; BackgroundImage(Img); BackgroundRepeat(RepeatBoth); } } switch (TagId) { default: 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)) { GDocumentEnv::LoadJob *j = Html->Environment->NewJob(); if (j) { LgiAssert(Html != NULL); GTag *t = this; j->Uri.Reset(NewStr(Href)); j->Env = Html->Environment; j->UserData = t; j->UserUid = Html->GetDocumentUid(); GDocumentEnv::LoadType Result = Html->Environment->GetContent(j); if (Result == GDocumentEnv::LoadImmediate) { GStreamI *s = j->GetStream(); if (s) { int Len = (int)s->GetSize(); if (Len > 0) { GAutoString 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 == GDocumentEnv::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(GCss::Len(GCss::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; GTag *Table = GetTable(); if (Table) { Len l = Table->_CellPadding(); if (!l.IsValid()) { l.Type = GCss::LenPx; l.Value = DefaultCellPadding; } PaddingLeft(l); PaddingRight(l); PaddingTop(l); PaddingBottom(l); } if (TagId == TAG_TH) FontWeight(GCss::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; } } 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 = GString(s).SplitDelimit(" \t"); } Restyle(); switch (TagId) { default: break; case TAG_BIG: { GCss::Len l; l.Type = SizeLarger; FontSize(l); break; } /* case TAG_META: { GAutoString 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 && LgiGetCsInfo(Cs)) { // Html->SetCharset(Cs); } } break; } */ case TAG_BODY: { GCss::ColorDef Bk = BackgroundColor(); if (Bk.Type != ColorInherit) { // Copy the background up to the GHtml wrapper Html->GetCss(true)->BackgroundColor(Bk); } /* GFont *f = GetFont(); if (FontSize().Type == LenInherit) { FontSize(Len(LenPt, (float)f->PointSize())); } */ break; } case TAG_HEAD: { Display(DispNone); break; } case TAG_PRE: { GFontType Type; if (Type.GetSystemFont("Fixed")) { LgiAssert(ValidStr(Type.GetFace())); FontFamily(StringsDef(Type.GetFace())); } break; } case TAG_TR: break; case TAG_TD: case TAG_TH: { LgiAssert(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)) { 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)) { char16 *cw = CleanText(s, strlen(s), "utf-8", true); char *c8 = WideToUtf8(cw); DeleteArray(cw); GToken Faces(c8, ","); DeleteArray(c8); char *face = TrimStr(Faces[0]); if (ValidStr(face)) { FontFamily(face); DeleteArray(face); } else { LgiTrace("%s:%i - No face for font tag.\n", __FILE__, __LINE__); } } 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(GCss::LenEm, 0.63f)); break; case 2: FontSize(Len(GCss::LenEm, 0.82f)); break; case 3: FontSize(Len(GCss::LenEm, 1.0f)); break; case 4: FontSize(Len(GCss::LenEm, 1.13f)); break; case 5: FontSize(Len(GCss::LenEm, 1.5f)); break; case 6: FontSize(Len(GCss::LenEm, 2.0f)); break; case 7: FontSize(Len(GCss::LenEm, 3.0f)); break; } } else { FontSize(Len(s)); } } break; } case TAG_SELECT: { if (!Html->InThread()) break; LgiAssert(!Ctrl); Ctrl = new GCombo(Html->d->NextCtrlId++, 0, 0, 100, SysFont->GetHeight() + 8, NULL); CtrlType = CtrlSelect; break; } case TAG_INPUT: { if (!Html->InThread()) break; LgiAssert(!Ctrl); const char *Type, *Value = NULL; Get("value", Value); GAutoWString 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) { GEdit *Ed; GAutoString UtfCleanValue(WideToUtf8(CleanValue)); Ctrl = Ed = new GEdit(Html->d->NextCtrlId++, 0, 0, 60, SysFont->GetHeight() + 8, UtfCleanValue); if (Ctrl) { Ed->Sunken(false); Ed->Password(CtrlType == CtrlPassword); } } else if (CtrlType == CtrlButton || CtrlType == CtrlSubmit) { GAutoString UtfCleanValue(WideToUtf8(CleanValue)); if (UtfCleanValue) { Ctrl = new InputButton(this, Html->d->NextCtrlId++, UtfCleanValue); } } } break; } } if (IsBlock()) { GCss::ImageDef bk = BackgroundImage(); if (bk.Type == GCss::ImageUri && ValidStr(bk.Uri)) { LoadImage(bk.Uri); } } if (Ctrl) { GFont *f = GetFont(); if (f) Ctrl->SetFont(f, false); } if (Display() == DispBlock && Html->Environment) { GCss::ImageDef Img = BackgroundImage(); if (Img.Type == ImageUri) { LoadImage(Img.Uri); } } } void GTag::SetCssStyle(const char *Style) { if (Style) { // Strip out comments char *Comment = 0; while ((Comment = strstr((char*)Style, "/*"))) { char *End = strstr(Comment+2, "*/"); if (!End) break; for (char *c = Comment; c<=End+2; c++) *c = ' '; } // Parse CSS const char *Ptr = Style; GCss::Parse(Ptr, GCss::ParseRelaxed); } } char16 *GTag::CleanText(const char *s, ssize_t Len, const char *SourceCs, bool ConversionAllowed, bool KeepWhiteSpace) { if (!s || Len <= 0) return NULL; static const char *DefaultCs = "iso-8859-1"; char16 *t = 0; bool DocAndCsTheSame = false; if (Html->DocCharSet && Html->Charset) { DocAndCsTheSame = _stricmp(Html->DocCharSet, Html->Charset) == 0; } if (SourceCs) { t = (char16*) LgiNewConvertCp(LGI_WideCharset, s, SourceCs, Len); } else if (Html->DocCharSet && Html->Charset && !DocAndCsTheSame && !Html->OverideDocCharset) { char *DocText = (char*)LgiNewConvertCp(Html->DocCharSet, s, Html->Charset, Len); t = (char16*) LgiNewConvertCp(LGI_WideCharset, DocText, Html->DocCharSet, -1); DeleteArray(DocText); } else if (Html->DocCharSet) { t = (char16*) LgiNewConvertCp(LGI_WideCharset, s, Html->DocCharSet, Len); } else { t = (char16*) LgiNewConvertCp(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++; } GAutoWString Var(NewStrW(i, e-i)); char16 Char = GHtmlStatic::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 *GTag::ParseText(char *Doc) { ColorDef c; c.Type = ColorRgb; c.Rgb32 = LC_WORKSPACE; BackgroundColor(c); TagId = TAG_BODY; Tag.Reset(NewStr("body")); Info = Html->GetTagInfo(Tag); char *OriginalCp = NewStr(Html->Charset); GStringPipe 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) { GTag *t = new GTag(Html, this); if (t) { t->Color(ColorDef(ColorRgb, Rgb24To32(LC_TEXT))); t->Text(Line); } } if (*s == '\n') { s++; GTag *t = new GTag(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); GAutoWString t(CleanText(s, e - s, NULL, false)); if (t) { Utf16.Push(t); } s = e; } } Html->SetCharset(OriginalCp); DeleteArray(OriginalCp); return 0; } bool GTag::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 (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); GAutoWString 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 *GTag::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 GHtml::CloseTag(GTag *t) { if (!t) return; OpenTags.Delete(t); } bool GTag::OnUnhandledColor(GCss::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 = GHtmlStatic::Inst->ColourMap.Find(tmp); s = e; if (m >= 0) { def->Type = GCss::ColorRgb; def->Rgb32 = Rgb24To32(m); return true; } return false; } void GTag::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 GTag::ResetCaches() { /* If during the parse process a callback causes a layout to happen then it's possible to have partial information in the GHtmlTableLayout 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 GHtmlTableLayout. That way when the first real layout happens all the data is there. */ if (Cell) DeleteObj(Cell->Cells); for (size_t i=0; iResetCaches(); } GdcPt2 GTag::GetTableSize() { GdcPt2 s(0, 0); if (Cell && Cell->Cells) { Cell->Cells->GetSize(s.x, s.y); } return s; } GTag *GTag::GetTableCell(int x, int y) { GTag *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 GTag::GetWidthMetrics(GTag *Table, uint16 &Min, uint16 &Max) { bool Status = true; int MarginPx = 0; int LineWidth = 0; if (Display() == GCss::DispNone) return true; // Break the text into words and measure... if (Text()) { int MinContent = 0; int MaxContent = 0; GFont *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) { GDisplayString ds(f, s, Len); MinContent = MAX(MinContent, ds.X()); } // Move to the next word. s = (*e) ? e + 1 : 0; } GDisplayString ds(f, Text()); LineWidth = MaxContent = ds.X(); } #ifdef _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 { GCss::BorderDef BLeft = BorderLeft(); GCss::BorderDef BRight = BorderRight(); GCss::Len PLeft = PaddingLeft(); GCss::Len PRight = PaddingRight(); MarginPx = (int)(PLeft.ToPx() + PRight.ToPx() + BLeft.ToPx()); if (Table->BorderCollapse() == GCss::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 { GdcPt2 s; GHtmlTableLayout c(this); c.GetSize(s.x, s.y); // Auto layout table GArray 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(GArray &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(GArray &a) { T s = 0; for (unsigned i=0; iCells) { #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT if (Debug) { //int asd=0; } #endif Cell->Cells = new GHtmlTableLayout(this); #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT if (Cell->Cells && Debug) Cell->Cells->Dump(); #endif } if (Cell->Cells) Cell->Cells->LayoutTable(f, Depth); } void GHtmlTableLayout::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; GArray 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 == GCss::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 / Growable.Length(); } LgiAssert(AddPx >= 0); MinCol[x] += AddPx; 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) / Growable.Length(); for (unsigned i=0; i= 0); } } } } 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 GHtmlTableLayout::DeallocatePx(int StartCol, int Cols, int MaxPx) { int TotalPx = GetTotalX(StartCol, Cols); if (TotalPx <= MaxPx || MaxPx == 0) return; int TrimPx = TotalPx - MaxPx; GArray 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); for (unsigned i=0; iGetFont(); Table->ZeroTableElements(); MinCol.Length(0); MaxCol.Length(0); MaxRow.Length(0); SizeCol.Length(0); GCss::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(); GCss::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() != GCss::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) { GCss::DisplayType Disp = t->Display(); if (Disp == GCss::DispNone) continue; GCss::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); 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' renders correctly. #if 0 DeallocatePx(0, 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; } GCss::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 == GCss::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; } } } } 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; 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 GArray 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); GRect Box(0, 0, -CellSpacing, 0); for (int i=0; iCell->Span.x; i++) { int ColSize = MinCol[x + i] + CellSpacing; LgiAssert(ColSize >= 0); if (ColSize < 0) break; t->Size.x += ColSize; XPos += ColSize; Box.x2 += ColSize; } GCss::Len Ht = t->Height(); GFlowRegion 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 != GCss::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; } else break; } } #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT if (Table->Debug) { LgiTrace("%s:%i - AfterCellFlow\n", _FL); for (unsigned i=0; iCell->Pos.x == x && t->Cell->Pos.y == y) { GCss::Len Ht = t->Height(); if (!(Ht.IsValid() && Ht.Type != GCss::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 GTag(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 GTag::TblCell)) { t->Cell->Pos.x = x; t->Cell->Pos.y = y; t->Cell->Span.x = 1; t->Cell->Span.y = 1; } t->BackgroundColor(GCss::ColorDef(GCss::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 GCss::AlignCenter: { int fx = f->X(); int Ox = (fx-Table->Size.x) >> 1; Table->Pos.x = f->x1 + MAX(Ox, 0); break; } case GCss::AlignRight: { Table->Pos.x = f->x2 - Table->Size.x; break; } default: { Table->Pos.x = f->x1; break; } } Table->Pos.y = f->y1; Table->Size.y = Cy + TablePadding.y2 + TableBorder.y2; } GRect GTag::ChildBounds() { GRect b(0, 0, -1, -1); for (unsigned i=0; iGetRect(); b.Union(&c); } else { b = t->GetRect(); } } return b; } GdcPt2 GTag::AbsolutePos() { GdcPt2 p; for (GTag *t=this; t; t=ToTag(t->Parent)) { p += t->Pos; } return p; } void GTag::SetSize(GdcPt2 &s) { Size = s; } GArea::~GArea() { DeleteObjects(); } GRect GArea::Bounds() { GRect n(0, 0, -1, -1); for (unsigned i=0; iLength(); i++) { GRect *r = (*c)[i]; if (!Top || (r && (r->y1 < Top->y1))) { Top = r; } } return Top; } void GArea::FlowText(GTag *Tag, GFlowRegion *Flow, GFont *Font, int LineHeight, char16 *Text, GCss::LengthType Align) { if (!Flow || !Text || !Font) return; 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. GFlowRect *Tr = new GFlowRect; 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(); LgiAssert(Tr->y2 >= Tr->y1); Flow->y2 = MAX(Flow->y2, Tr->y2+1); Flow->cx = Tr->x2 + 1; Add(Tr); Flow->Insert(Tr); return; } #endif while (*Text) { GFlowRect *Tr = new GFlowRect; 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; GDisplayString 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(Align); goto Restart; } } else { Tr->Len = n; LgiAssert(Tr->Len > 0); Wrap = true; } } else { // Fits.. Tr->Len = Chars; LgiAssert(Tr->Len > 0); } GDisplayString 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); 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; } } 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; LgiAssert(0); return 0; } bool GTag::Serialize(GXmlTag *t, bool Write) { GRect 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) { GStringPipe p(256); for (char16 *c = Txt; *c; c++) { if (*c > ' ' && *c < 127 && !strchr("%<>\'\"", *c)) p.Print("%c", (char)*c); else p.Print("%%%.4x", *c); } GAutoString Tmp(p.NewStr()); t->SetContent(Tmp); } if (Props.Length()) { GAutoString CssStyles = ToString(); LgiAssert(!strchr(CssStyles, '\"')); t->SetAttr("style", CssStyles); } if (Html->Cursor == this) { LgiAssert(Cursor >= 0); t->SetAttr("cursor", (int64)Cursor); } else LgiAssert(Cursor < 0); if (Html->Selection == this) { LgiAssert(Selection >= 0); t->SetAttr("selection", (int64)Selection); } else LgiAssert(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())) { GStringPipe 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) { LgiAssert(Html->Cursor == NULL); Html->Cursor = this; Cursor = atoi(s); LgiAssert(Cursor >= 0); } s = t->GetAttr("selection"); if (s) { LgiAssert(Html->Selection == NULL); Html->Selection = this; Selection = atoi(s); LgiAssert(Selection >= 0); } #ifdef _DEBUG s = t->GetAttr("debug"); if (s && atoi(s) != 0) Debug = true; #endif for (int i=0; iChildren.Length(); i++) { GXmlTag *child = t->Children[i]; if (child->IsTag("e")) { GTag *tag = new GTag(Html, NULL); if (!tag) { LgiAssert(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 GTag::CenterText() { if (!Parent) return; // Find the size of the text elements. int ContentPx = 0; for (unsigned i=0; iX(); } GFont *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 GTag::OnFlow(GFlowRegion *Flow, uint16 Depth) { if (Depth >= MAX_RECURSION_DEPTH) return; DisplayType Disp = Display(); if (Disp == DispNone) return; GFont *f = GetFont(); GFlowRegion Local(*Flow); bool Restart = true; int BlockFlowWidth = 0; const char *ImgAltText = NULL; Size.x = 0; Size.y = 0; GCssTools Tools(this, f); GRect rc(Flow->X(), Html->Y()); PadPx = Tools.GetPadding(rc); switch (TagId) { default: break; case TAG_BODY: { Flow->InBody++; break; } case TAG_IFRAME: { GFlowRegion Temp = *Flow; Flow->EndBlock(GetAlign(true)); 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; GCss::Len w = Width(); GCss::Len h = Height(); // GCss::Len MinX = MinWidth(); // GCss::Len MaxX = MaxWidth(); GCss::Len MinY = MinHeight(); GCss::Len MaxY = MaxHeight(); GAutoPtr a; int ImgX, ImgY; if (Image) { ImgX = Image->X(); ImgY = Image->Y(); } else if (Get("alt", ImgAltText) && ValidStr(ImgAltText)) { GDisplayString 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(GetAlign(true)); } Pos.y = Flow->y1; Flow->y2 = MAX(Flow->y1, Pos.y + Size.y - 1); GCss::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(GetAlign(true)); Pos.x = Flow->x1; Pos.y = Flow->y1 + 7; Size.x = Flow->X(); Size.y = 2; Flow->cx ++; Flow->y2 += 16; Flow->FinishLine(GetAlign(true)); return; break; } case TAG_TABLE: { Flow->EndBlock(GetAlign(true)); GCss::Len left = GetCssLen(MarginLeft, Margin); GCss::Len top = GetCssLen(MarginTop, Margin); GCss::Len right = GetCssLen(MarginRight, Margin); GCss::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(GetAlign(true)); } /* if (Debug) LgiTrace("Before %s\n", Flow->ToString().Get()); */ BlockFlowWidth = Flow->X(); // Indent the margin... GCss::Len left = GetCssLen(MarginLeft, Margin); GCss::Len top = GetCssLen(MarginTop, Margin); GCss::Len right = GetCssLen(MarginRight, Margin); GCss::Len bottom = GetCssLen(MarginBottom, Margin); Flow->Indent(this, left, top, right, bottom, true); // Set the width if any if (Disp == DispBlock) { GCss::Len Wid = Width(); if (!IsTableCell(TagId) && Wid.IsValid()) Size.x = Flow->ResolveX(Wid, this, false); else if (TagId != TAG_IMG) { if (Flow->Inline) Size.x = 0; // block inside inline-block default to fit the content else Size.x = Flow->X(); } if (MaxWidth().IsValid()) { int Px = Flow->ResolveX(MaxWidth(), this, false); if (Size.x > Px) Size.x = Px; } Pos.x = Flow->x1; } else { Size.x = 0; // Child content should expand this to fit Pos.x = Flow->cx; } 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, GCss::BorderLeft(), GCss::BorderTop(), GCss::BorderRight(), GCss::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()) { GCss::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(GHtmlListItem)); 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) { GCss::PropMap Map; GCss Final; Map.Add(PropLineHeight, new GCss::PropArray); for (GTag *t = this; t; t = t->TagId == TAG_TABLE ? NULL : ToTag(t->Parent)) { if (!Final.InheritCollect(*t, Map)) break; } Final.InheritResolve(Map); Map.DeleteObjects(); GCss::Len CssLineHeight = Final.LineHeight(); if (f) { int FontPx = FontPxHeight(f); if (!CssLineHeight.IsValid() || CssLineHeight.Type == GCss::LenAuto || CssLineHeight.Type == GCss::LenNormal) { LineHeightCache = f->GetHeight(); } else { LineHeightCache = CssLineHeight.ToPx(FontPx, f); } } } // Flow in the rest of the text... char16 *Txt = Text(); GCss::LengthType Align = GetAlign(true); TextPos.FlowText(this, Flow, f, LineHeightCache, Txt, Align); } } // Flow children for (unsigned i=0; iPosition()) { case PosStatic: case PosAbsolute: case PosFixed: { GFlowRegion 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()); } } GCss::LengthType XAlign = GetAlign(true); if (Disp == DispBlock || Disp == DispInlineBlock) { GCss::Len Ht = Height(); GCss::Len MaxHt = MaxHeight(); // I dunno, there should be a better way... :-( if (MarginLeft().Type == LenAuto && MarginRight().Type == LenAuto) { XAlign = GCss::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; } } if (Disp == DispBlock) { Flow->EndBlock(XAlign); int OldFlowSize = Flow->x2 - Flow->x1 + 1; Flow->Outdent(this, PaddingLeft(), PaddingTop(), PaddingRight(), PaddingBottom(), false); Flow->Outdent(this, GCss::BorderLeft(), GCss::BorderTop(), GCss::BorderRight(), GCss::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 { GCss::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; Flow->y2 = MAX(Local.y2, Local.y1 + Size.y); if (!IsTableTag()) Flow->Inline--; CenterText(); } if (XAlign == GCss::AlignCenter) { int OffX = (Flow->x2 - Flow->x1 - Size.x) >> 1; if (OffX > 0) { Pos.x += OffX; } } else if (XAlign == GCss::AlignRight) { int OffX = Flow->x2 - Flow->x1 - Size.x; if (OffX > 0) { Pos.x += OffX; } } } else { Flow->Outdent(PadPx, false); switch (TagId) { default: break; case TAG_SELECT: case TAG_INPUT: { if (Html->InThread() && Ctrl) { GRect 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(GetAlign(true)); Size.y = Flow->y2 - OldFlowY2; Flow->y2 = MAX(Flow->y2, Flow->y1 + Size.y - 1); break; } case TAG_CENTER: { int Px = Flow->X(); for (GHtmlElement **e = NULL; Children.Iterate(e); ) { GTag *t = ToTag(*e); if (t->IsBlock()) { if (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 (Debug) LgiTrace("After %s\n", Flow->ToString().Get()); */ if (TagId == TAG_BODY && Flow->InBody > 0) { Flow->InBody--; } } bool GTag::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; } GTag *GTag::GetTable() { GTag *t = 0; for (t=ToTag(Parent); t && t->TagId != TAG_TABLE; t = ToTag(t->Parent)) ; return t; } void GTag::BoundParents() { if (!Parent) return; GTag *np; for (GTag *n=this; n; n = np) { np = ToTag(n->Parent); if (!np || n->Parent->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 { GSurface *pDC; - uint32 LineStyle; - uint32 LineReset; - uint32 OldStyle; + uint32_t LineStyle; + uint32_t LineReset; + uint32_t OldStyle; DrawBorder(GSurface *pdc, GCss::BorderDef &d) { LineStyle = 0xffffffff; LineReset = 0x80000000; if (d.Style == GCss::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 GTag::GetInlineRegion(GRegion &rgn) { if (TagId == TAG_IMG) { GRect rc(0, 0, Size.x-1, Size.y-1); rc.Offset(Pos.x, Pos.y); rgn.Union(&rc); } else { for (unsigned i=0; iGetInlineRegion(rgn); } } class CornersImg : public GMemDC { public: int Px, Px2; CornersImg( float RadPx, GRect *BorderPx, GCss::BorderDef **defs, GColour &Back, bool DrawBackground) { Px = 0; Px2 = 0; //Radius.Type != GCss::LenInherit && if (RadPx > 0.0f) { Px = (int)ceil(RadPx); Px2 = Px << 1; if (Create(Px2, Px2, System32BitColourSpace)) { #if 1 Colour(0, 32); #else Colour(GColour(255, 0, 255)); #endif Rectangle(); GPointF ctr(Px, Px); GPointF LeftPt(0.0, Px); GPointF TopPt(Px, 0.0); GPointF RightPt(X(), Px); GPointF 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}; GPointF *pts[4] = {&LeftPt, &TopPt, &RightPt, &BottomPt}; // Draw border parts.. for (int i=0; i<4; i++) { int k = (i + 1) % 4; // Setup the stops GBlendStop stops[2] = { {0.0, 0}, {1.0, 0} }; - uint32 iColour = defs[i]->Color.IsValid() ? defs[i]->Color.Rgb32 : Back.c32(); - uint32 kColour = defs[k]->Color.IsValid() ? defs[k]->Color.Rgb32 : Back.c32(); + 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 GLinearBlendBrush br ( *pts[i], *pts[k], 2, stops ); // Setup the clip GRect 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... GPath 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) { GSolidBrush br(Back); p.Fill(this, br); } else { GEraseBrush br; p.Fill(this, br); } ClipRgn(NULL); } #ifdef MAC ConvertPreMulAlpha(true); #endif #if 0 static int count = 0; GString file; file.Printf("c:\\temp\\img-%i.bmp", ++count); GdcD->Save(file, Corners); #endif } } } }; void GTag::PaintBorderAndBackground(GSurface *pDC, GColour &Back, GRect *BorderPx) { GArray r; GRect 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. GFont *f = GetFont(); BorderDef Left = BorderLeft(); BorderPx->x1 = Left.ToPx(Size.x, f); BorderDef Top = BorderTop(); BorderPx->y1 = Top.ToPx(Size.x, f); BorderDef Right = BorderRight(); BorderPx->x2 = Right.ToPx(Size.x, f); BorderDef Bottom = BorderBottom(); BorderPx->y2 = Bottom.ToPx(Size.x, f); GCss::BorderDef *defs[4] = {&Left, &Top, &Right, &Bottom}; // Work out the rectangles switch (Display()) { case DispInlineBlock: case DispBlock: { r[0].ZOff(Size.x-1, Size.y-1); break; } case DispInline: { GRegion rgn; GetInlineRegion(rgn); // rgn.Simplify(false); for (int i=0; ix1 + PadPx.x1; rc.y1 -= BorderPx->y1 + PadPx.y1; rc.x2 += BorderPx->x2 + PadPx.x2; rc.y2 += BorderPx->y2 + PadPx.y2; } r.New() = rc; } break; } default: return; } // If we are drawing rounded corners, draw them into a memory context GAutoPtr Corners; int Px = 0, Px2 = 0; GCss::Len Radius = BorderRadius(); float RadPx = Radius.Type == GCss::LenPx ? Radius.Value : Radius.ToPx(Size.x, GetFont()); bool HasRadius = Radius.Type != GCss::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 GRect 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(GColour(255, 0, 0, 0x80)); pDC->Rectangle(rc.x1+Px, rc.y1, rc.x2-Px, rc.y2); pDC->Colour(GColour(0, 255, 0, 0x80)); pDC->Rectangle(rc.x1, rc.y1+Px, rc.x1+Px-1, rc.y2-Px); pDC->Colour(GColour(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); /* if (Debug) { pDC->Colour(GColour(255, 0, 0)); pDC->Box(&rc); } */ } GCss::BorderDef *b; if ((b = &Left)->IsValid()) { 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)->IsValid()) { 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)->IsValid()) { 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)->IsValid()) { 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(GSurface *pDC, GRect *r, GSurface *Image, GCss::RepeatType Repeat) { int Px = 0, Py = 0; int Old = pDC->Op(GDC_ALPHA); if (!Image) return; switch (Repeat) { default: case GCss::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 GCss::RepeatX: { for (int x=0; xX(); x += Image->X()) { pDC->Blt(Px + x, Py, Image); } break; } case GCss::RepeatY: { for (int y=0; yY(); y += Image->Y()) { pDC->Blt(Px, Py + y, Image); } break; } case GCss::RepeatNone: { pDC->Blt(Px, Py, Image); break; } } pDC->Op(Old); } void GTag::OnPaint(GSurface *pDC, bool &InSelection, uint16 Depth) { if (Depth >= MAX_RECURSION_DEPTH || Display() == DispNone) return; int Px, Py; pDC->GetOrigin(Px, Py); switch (TagId) { case TAG_INPUT: case TAG_SELECT: { if (Ctrl) { int Sx = 0, Sy = 0; int LineY = GetFont()->GetHeight(); Html->GetScrollPos(Sx, Sy); Sx *= LineY; Sy *= LineY; GRect r(0, 0, Size.x-1, Size.y-1), Px; GColour 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() - Sx, AbsY() - Sy); Ctrl->SetPos(r); } if (TagId == TAG_SELECT) return; break; } case TAG_BODY: { COLOUR b = GetBack(); if (b != GT_TRANSPARENT) { pDC->Colour(b, 32); pDC->Rectangle(Pos.x, Pos.y, Pos.x+Size.x, Pos.y+Size.y); } if (Image) { GRect 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(LC_MED, 24); 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: { GRect 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; GColourSpace Cs = Image->GetColourSpace(); if (Cs == CsIndex8 && Image->AlphaDC()) Cs = System32BitColourSpace; GAutoPtr r(new GMemDC(Size.x, Size.y, Cs)); if (r) { if (Cs == CsIndex8) r->Palette(new GPalette(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) { GRect b(0, 0, Size.x-1, Size.y-1); GColour Fill(GdcMixColour(LC_MED, LC_LIGHT, 0.2f), 24); GColour Border(LC_MED, 24); // Border pDC->Colour(Border); pDC->Box(&b); b.Size(1, 1); pDC->Box(&b); b.Size(1, 1); pDC->Colour(Fill); pDC->Rectangle(&b); const char *Alt; GColour Red(GdcMixColour(Rgb24(255, 0, 0), Fill.c24(), 0.3f), 24); if (Get("alt", Alt) && ValidStr(Alt)) { GDisplayString 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); GRect 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: { GColour fore = _Colour(true); GColour back = _Colour(false); if (Display() == DispBlock && Html->Environment) { GCss::ImageDef Img = BackgroundImage(); if (Img.Img) { GRect 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); GFont *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 peice of text int LineHtOff = ((EffectiveLineHt - FontPx + 1) >> 1) - LeadingPx; #define FontColour(InSelection) \ f->Transparent(!InSelection && !IsEditor); \ if (InSelection) \ f->Colour(LC_FOCUS_SEL_FORE, LC_FOCUS_SEL_BACK); \ else \ { \ GColour bk(back.IsTransparent() ? GColour(LC_WORKSPACE, 24) : back); \ GColour fr(fore.IsTransparent() ? GColour(DefaultTextColour, 32) : fore); \ if (IsEditor) \ bk = bk.Mix(GColour(0, 0, 0), 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; } GRect 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 GDisplayString ds(f, Tr->Text + Done, c); if (IsEditor) { GRect 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(LC_TEXT, 24); pDC->Rectangle(&CursorPos); } } else if (Cursor >= 0) { FontColour(InSelection); ssize_t Base = GetTextStart(); for (unsigned i=0; iText - Text()) - Base; LgiAssert(Tr->y2 >= Tr->y1); GDisplayString 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(LC_TEXT, 24); GRect c; if (Off) { GDisplayString 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 (unsigned i=0; iText, Tr->Len); ds.Draw(pDC, Tr->x1, Tr->y1 + LineHtOff, IsEditor ? Tr : NULL); } } } break; } } #if DEBUG_TABLE_LAYOUT && 0 if (IsTableCell(TagId)) { GTag *Tbl = this; while (Tbl->TagId != TAG_TABLE && Tbl->Parent) Tbl = Tbl->Parent; if (Tbl && Tbl->TagId == TAG_TABLE && Tbl->Debug) { pDC->Colour(GColour(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) { GTag *Tbl = this; while (Tbl && Tbl->TagId != TAG_TABLE) Tbl = ToTag(Tbl->Parent); if (Tbl && Tbl->Debug) { int Ls = pDC->LineStyle(GSurface::LineDot); pDC->Colour(GColour::Blue); pDC->Box(0, 0, Size.x-1, Size.y-1); pDC->LineStyle(Ls); } } #endif } ////////////////////////////////////////////////////////////////////// GHtml::GHtml(int id, int x, int y, int cx, int cy, GDocumentEnv *e) : GDocView(e), ResObject(Res_Custom), GHtmlParser(NULL) { View = this; d = new GHtmlPrivate; SetReadOnly(true); ViewWidth = -1; SetId(id); GRect r(x, y, x+cx, y+cy); SetPos(r); Cursor = 0; Selection = 0; DocumentUid = 0; _New(); } GHtml::~GHtml() { _Delete(); DeleteObj(d); if (JobSem.Lock(_FL)) { JobSem.Jobs.DeleteObjects(); JobSem.Unlock(); } } void GHtml::_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 GFont *Def = new GFont; if (Def) { if (Def->CreateFromCss(DefaultFont)) SetFont(Def, true); else DeleteObj(Def); } #endif FontCache = new GFontCache(this); SetScrollBars(false, false); } void GHtml::_Delete() { LgiAssert(!d->IsParsing); CssStore.Empty(); CssHref.Empty(); OpenTags.Length(0); Source.Reset(); DeleteObj(Tag); DeleteObj(FontCache); } GFont *GHtml::DefFont() { return GetFont(); } void GHtml::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]; sprintf_s(p, sizeof(p), "c:\\temp\\css_parse_failure_%i.txt", LgiRand()); GFile 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) { GStringPipe p; CssStore.Dump(p); GAutoString a(p.NewStr()); GFile f; if (f.Open("C:\\temp\\css.txt", O_WRITE)) { f.Write(a, strlen(a)); f.Close(); } } #endif } } void GHtml::ParseDocument(const char *Doc) { if (!Tag) { Tag = new GTag(this, 0); } if (GetCss()) GetCss()->DeleteProp(GCss::PropBackgroundColor); if (Tag) { Tag->TagId = ROOT; OpenTags.Length(0); if (IsHtml) { Parse(Tag, Doc); // Add body tag if not specified... GTag *Html = Tag->GetTagByName("html"); GTag *Body = Tag->GetTagByName("body"); if (!Html && !Body) { if ((Html = new GTag(this, 0))) Html->SetTag("html"); if ((Body = new GTag(this, Html))) Body->SetTag("body"); Html->Attach(Body); if (Tag->Text()) { GTag *Content = new GTag(this, Body); if (Content) { Content->TagId = CONTENT; Content->Text(NewStrW(Tag->Text())); } } while (Tag->Children.Length()) { GTag *t = ToTag(Tag->Children.First()); Body->Attach(t, Body->Children.Length()); } DeleteObj(Tag); Tag = Html; } else if (!Body) { if ((Body = new GTag(this, Html))) Body->SetTag("body"); for (unsigned i=0; iChildren.Length(); i++) { GTag *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)) { GTag *Content = new GTag(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 (GTag *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) { GTag *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 GHtml::NameW(const char16 *s) { GAutoPtr utf(WideToUtf8(s)); return Name(utf); } char16 *GHtml::NameW() { GBase::Name(Source); return GBase::NameW(); } bool GHtml::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; } char *GHtml::Name() { #if LUIS_DEBUG LgiTrace("%s:%i html(%p).src(%p)='%30.30s'\n", _FL, this, Source, Source); #endif if (!Source && Tag) { GStringPipe s(1024); Tag->CreateSource(s); Source.Reset(s.NewStr()); } return Source; } GMessage::Result GHtml::OnEvent(GMessage *Msg) { switch (MsgCode(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; iUserUid == MyUid && j->UserData != NULL) { Html1::GTag *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 (j->pDC) { r->SetImage(j->Uri, j->pDC.Release()); ViewWidth = 0; Update = true; } else if (r->TagId == TAG_LINK) { if (!CssHref.Find(j->Uri)) { GStreamI *s = j->GetStream(); if (s) { int Size = (int)s->GetSize(); GAutoString 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 { Html1::GTag *p = ToTag(r->Parent); while (p->Parent) p = ToTag(p->Parent); int asd=0; } } // else it's from another (historical) HTML control, ignore } JobSem.Jobs.DeleteObjects(); JobSem.Unlock(); } if (InitDeferredLoads > 0 && d->DeferredLoads <= 0) { LgiAssert(d->DeferredLoads == 0); d->DeferredLoads = 0; OnLoad(); } if (Update) { OnPosChange(); Invalidate(); } break; } } return GDocView::OnEvent(Msg); } int GHtml::OnNotify(GViewI *c, int f) { switch (c->GetId()) { case IDC_VSCROLL: { int LineY = GetFont()->GetHeight(); if (f == GNotifyScrollBar_Create && VScroll && LineY > 0) { int y = Y(); int p = MAX(y / LineY, 1); int fy = d->Content.y / LineY; VScroll->SetPage(p); VScroll->SetLimits(0, fy); } Invalidate(); break; } default: { GTag *Ctrl = Tag ? Tag->FindCtrlId(c->GetId()) : NULL; if (Ctrl) return Ctrl->OnNotify(f); break; } } return GLayout::OnNotify(c, f); } void GHtml::OnPosChange() { GLayout::OnPosChange(); if (ViewWidth != X()) { Invalidate(); } } GdcPt2 GHtml::Layout(bool ForceLayout) { GRect Client = GetClient(); if (Tag && (ViewWidth != Client.X() || ForceLayout)) { GFlowRegion 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 = LgiCurrentTime(); 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->SetLimits(0, fy); } } else { // LgiTrace("%s - Dropping SetScroll, loop detected: %i ms\n", GetClass(), (int)(Now - d->SetScrollTime)); } } return d->Content; } void GHtml::OnPaint(GSurface *ScreenDC) { #if LGI_EXCEPTIONS try { #endif #if GHTML_USE_DOUBLE_BUFFER GRect Client = GetClient(); if (!MemDC || (MemDC->X() < Client.X() || MemDC->Y() < Client.Y())) { if (MemDC.Reset(new GMemDC)) { 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(GColour(255, 0, 255)); MemDC->Rectangle(); #endif } #endif GSurface *pDC = MemDC ? MemDC : ScreenDC; GColour cBack; if (GetCss()) { GCss::ColorDef Bk = GetCss()->BackgroundColor(); if (Bk.Type == GCss::ColorRgb) cBack = Bk; } if (!cBack.IsValid()) cBack.Set(Enabled() ? LC_WORKSPACE : LC_MED, 24); 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; Tag->OnPaint(pDC, InSelection, 0); } #if GHTML_USE_DOUBLE_BUFFER if (MemDC) { pDC->SetOrigin(0, 0); #if 0 pDC->Colour(Rgb24(255, 0, 0), 24); pDC->Line(0, 0, X()-1, Y()-1); pDC->Line(X()-1, 0, 0, Y()-1); #endif ScreenDC->Blt(0, 0, MemDC); } #endif #if LGI_EXCEPTIONS } catch (...) { LgiTrace("GHtml paint crash\n"); } #endif if (d->OnLoadAnchor && VScroll) { GAutoString a = d->OnLoadAnchor; GotoAnchor(a); LgiAssert(d->OnLoadAnchor == 0); } } bool GHtml::HasSelection() { if (Cursor && Selection) { return Cursor->Cursor >= 0 && Selection->Selection >= 0 && !(Cursor == Selection && Cursor->Cursor == Selection->Selection); } return false; } void GHtml::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 GHtml::SelectAll() { } GTag *GHtml::GetLastChild(GTag *t) { if (t && t->Children.Length()) { for (GTag *i = ToTag(t->Children.Last()); i; ) { GTag *c = i->Children.Length() ? ToTag(i->Children.Last()) : NULL; if (c) i = c; else return i; } } return 0; } GTag *GHtml::PrevTag(GTag *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 (GTag *p = t; p; p = ToTag(p->Parent)) { // Does this tag have a parent? if (p->Parent) { // Prev? GTag *pp = ToTag(p->Parent); ssize_t Idx = pp->Children.IndexOf(p); GTag *Prev = Idx > 0 ? ToTag(pp->Children[Idx - 1]) : NULL; if (Prev) { GTag *Last = GetLastChild(Prev); return Last ? Last : Prev; } else { return ToTag(p->Parent); } } } return 0; } GTag *GHtml::NextTag(GTag *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 (GTag *p = t; p; p = ToTag(p->Parent)) { // Does this tag have a next? if (p->Parent) { GTag *pp = ToTag(p->Parent); size_t Idx = pp->Children.IndexOf(p); GTag *Next = pp->Children.Length() > Idx + 1 ? ToTag(pp->Children[Idx + 1]) : NULL; if (Next) { return Next; } } } } return 0; } int GHtml::GetTagDepth(GTag *Tag) { // Returns the depth of the tag in the tree. int n = 0; for (GTag *t = Tag; t; t = ToTag(t->Parent)) { n++; } return n; } bool GHtml::IsCursorFirst() { if (!Cursor || !Selection) return false; return CompareTagPos(Cursor, Cursor->Cursor, Selection, Selection->Selection); } bool GHtml::CompareTagPos(GTag *a, ssize_t AIdx, GTag *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 { GArray ATree, BTree; for (GTag *t = a; t; t = ToTag(t->Parent)) ATree.AddAt(0, t); for (GTag *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); GTag *p = ATree[i-1]; LgiAssert(BTree[i-1] == p); ssize_t ai = p->Children.IndexOf(at); ssize_t bi = p->Children.IndexOf(bt); return ai < bi; } } } return false; } void GHtml::SetLoadImages(bool i) { if (i ^ GetLoadImages()) { GDocView::SetLoadImages(i); SendNotify(GNotifyShowImagesChanged); if (GetLoadImages() && Tag) { Tag->LoadImages(); } } } char *GHtml::GetSelection() { char *s = 0; if (Cursor && Selection) { GMemQueue 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; iOnFind(Dlg); } void BuildTagList(GArray &t, GTag *Tag) { t.Add(Tag); for (unsigned i=0; iChildren.Length(); i++) { GTag *c = ToTag(Tag->Children[i]); BuildTagList(t, c); } } static void FormEncode(GStringPipe &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 GHtml::OnSubmitForm(GTag *Form) { if (!Form || !Environment) { LgiAssert(!"Bad param"); return false; } const char *Method = NULL; const char *Action = NULL; if (!Form->Get("method", Method) || !Form->Get("action", Action)) { LgiAssert(!"Missing form action/method"); return false; } LHashTbl,char*> f; Form->CollectFormValues(f); bool Status = false; if (!_stricmp(Method, "post")) { GStringPipe 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); } GAutoPtr Data(p.NewStr()); Status = Environment->OnPostForm(this, Action, Data); } else if (!_stricmp(Method, "get")) { Status = Environment->OnNavigate(this, Action); } else { LgiAssert(!"Bad form method."); } f.DeleteArrays(); return Status; } bool GHtml::OnFind(GFindReplaceCommon *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) { GArray 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; VScroll->Value(Val); } Invalidate(); Status = true; break; } } } } return Status; } bool GHtml::OnKey(GKey &k) { bool Status = false; if (k.Down()) { int Dy = 0; int LineY = GetFont()->GetHeight(); int Page = GetClient().Y() / LineY; switch (k.c16) { case 'f': case 'F': { if (k.Modifier()) { GFindDlg Dlg(this, 0, FindCallback, this); Dlg.DoModal(); Status = true; } break; } case VK_F3: { OnFind(NULL); break; } case 'c': case 'C': #ifdef WIN32 case VK_INSERT: #endif { printf("Got 'c', mod=%i\n", k.Modifier()); if (k.Modifier()) { Copy(); Status = true; } break; } case VK_UP: { Dy = -1; Status = true; break; } case VK_DOWN: { Dy = 1; Status = true; break; } case VK_PAGEUP: { Dy = -Page; Status = true; break; } case VK_PAGEDOWN: { Dy = Page; Status = true; break; } case VK_HOME: { Dy = (int) (VScroll ? -VScroll->Value() : 0); Status = true; break; } case VK_END: { if (VScroll) { int64 Low, High; VScroll->Limits(Low, High); Dy = (int) ((High - Page) - VScroll->Value()); } Status = true; break; } } if (Dy && VScroll) { VScroll->Value(VScroll->Value() + Dy); Invalidate(); } } return Status; } int GHtml::ScrollY() { return GetFont()->GetHeight() * (VScroll ? (int)VScroll->Value() : 0); } void GHtml::OnMouseClick(GMouse &m) { Capture(m.Down()); SetPulse(m.Down() ? 200 : -1); if (m.Down()) { Focus(true); int Offset = ScrollY(); bool TagProcessedClick = false; GTagHit 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(GNotifySelectionChanged); } } 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(GNotifySelectionChanged); } 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()) { GSubMenu 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 (LgiLoadString(L_TEXTCTRL_COPY, "Copy"), IDM_COPY, HasSelection()); GMenuItem *Vs = RClick.AppendItem (LgiLoadString(L_VIEW_SOURCE, "View Source"), IDM_VIEW_SRC, Source != 0); RClick.AppendItem (LgiLoadString(L_COPY_SOURCE, "Copy Source"), IDM_COPY_SRC, Source != 0); GMenuItem *Load = RClick.AppendItem (LgiLoadString(L_VIEW_IMAGES, "View External Images"), IDM_VIEW_IMAGES, true); if (Load) Load->Checked(GetLoadImages()); RClick.AppendItem (LgiLoadString(L_VIEW_IN_DEFAULT_BROWSER, "View in Default Browser"), IDM_EXTERNAL, Source != 0); GSubMenu *Cs = RClick.AppendSub (LgiLoadString(L_CHANGE_CHARSET, "Change Charset")); if (Cs) { int n=0; for (GCharset *c = LgiGetCsList(); 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) { GClipBoard c(this); const char *ViewCs = GetCharset(); if (ViewCs) { GAutoWString w((char16*)LgiNewConvertCp(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) { GAutoWString s = Tag->DumpW(); if (s) { GClipBoard 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]; 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", LgiRand(1000000)); LgiMakePath(Path, sizeof(Path), Path, f); GFile F; if (!F.Open(Path, O_WRITE)) { LgiTrace("%s:%i - Failed to open '%s' for writing.\n", _FL, Path); break; } GStringPipe Ex; bool Error = false; F.SetSize(0); GAutoWString SrcMem; const char *ViewCs = GetCharset(); if (ViewCs) SrcMem.Reset((char16*)LgiNewConvertCp(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] = ""; if (Environment) { GDocumentEnv::LoadJob *j = Environment->NewJob(); if (j) { j->Uri.Reset(WideToUtf8(cid)); j->Env = Environment; j->Pref = GDocumentEnv::LoadJob::FmtFilename; j->UserUid = GetDocumentUid(); GDocumentEnv::LoadType Result = Environment->GetContent(j); if (Result == GDocumentEnv::LoadImmediate) { if (j->Filename) strcpy_s(File, sizeof(File), j->Filename); } else if (Result == GDocumentEnv::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:///"); GAutoWString 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); GAutoWString w(Ex.NewStrW()); GAutoString u(WideToUtf8(w, WideChars)); if (u) F.Write(u, strlen(u)); F.Close(); GAutoString Err; if (!LgiExecute(Path, NULL, NULL, &Err)) { LgiMsg( this, "Failed to open '%s'\n%s", LgiApp ? LgiApp->GBase::Name() : GetClass(), MB_OK, Path, Err.Get()); } } break; } default: { if (Id >= IDM_CHARSET_BASE) { GCharset *c = LgiGetCsList() + (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(GNotifyCharsetChanged); } } else { OnContextMenuCommand(Hit, Id); } break; } } } } } else // Up Click { if (Selection && Cursor && Selection == Cursor && Selection->Selection == Cursor->Cursor) { Selection->Selection = -1; Selection = 0; SendNotify(GNotifySelectionChanged); #if DEBUG_SELECTION LgiTrace("NoSelect on release\n"); #endif } } } void GHtml::OnLoad() { d->IsLoaded = true; SendNotify(GNotifyDocLoaded); } GTag *GHtml::GetTagByPos(int x, int y, ssize_t *Index, GdcPt2 *LocalCoords, bool DebugLog) { GTag *Status = NULL; if (Tag) { if (DebugLog) LgiTrace("GetTagByPos starting...\n"); GTagHit 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; } bool GHtml::OnMouseWheel(double Lines) { if (VScroll) { VScroll->Value(VScroll->Value() + (int)Lines); Invalidate(); } return true; } LgiCursor GHtml::GetCursor(int x, int y) { int Offset = ScrollY(); ssize_t Index = -1; GdcPt2 LocalCoords; GTag *Tag = GetTagByPos(x, y + Offset, &Index, &LocalCoords); if (Tag) { GAutoString Uri; if (LocalCoords.x >= 0 && LocalCoords.y >= 0 && Tag->IsAnchor(&Uri)) { GRect c = GetClient(); c.Offset(-c.x1, -c.y1); if (c.Overlap(x, y) && ValidStr(Uri)) { return LCUR_PointingHand; } } } return LCUR_Normal; } void GHtml::OnMouseMove(GMouse &m) { if (!Tag) return; int Offset = ScrollY(); GTagHit Hit; Tag->GetTagByPos(Hit, m.x, m.y + Offset, 0, false); if (!Hit.Direct && !Hit.NearestText) return; GAutoString Uri; GTag *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); } GRect r = HitTag->GetRect(false); r.Offset(0, -Offset); 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(GNotifySelectionChanged); #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(GNotifySelectionChanged); } } } void GHtml::OnPulse() { if (VScroll && IsCapturing()) { int Fy = DefFont() ? DefFont()->GetHeight() : 16; GMouse m; if (GetMouse(m, false)) { GRect 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->Value(VScroll->Value() + Lines); Invalidate(); } } } } GRect *GHtml::GetCursorPos() { return &d->CursorPos; } void GHtml::SetCursorVis(bool b) { if (d->CursorVis ^ b) { d->CursorVis = b; Invalidate(); } } bool GHtml::GetCursorVis() { return d->CursorVis; } GDom *ElementById(GTag *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++) { GTag *c = ToTag(t->Children[i]); GDom *n = ElementById(c, id); if (n) return n; } } return 0; } GDom *GHtml::getElementById(char *Id) { return ElementById(Tag, Id); } bool GHtml::GetLinkDoubleClick() { return d->LinkDoubleClick; } void GHtml::SetLinkDoubleClick(bool b) { d->LinkDoubleClick = b; } bool GHtml::GetFormattedContent(const char *MimeType, GString &Out, GArray *Media) { if (!MimeType) { LgiAssert(!"No MIME type for getting formatted content"); return false; } if (!_stricmp(MimeType, "text/html")) { // We can handle this type... GArray 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)LgiCurrentTime(), (unsigned)LgiRand()); Img->Set("cid", id); Img->Get("cid", Cid); } if (Src && Cid) { GFile *f = new GFile; if (f) { if (f->Open(Src, O_READ)) { // Add the exported image stream to the media array GDocView::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... GStringPipe p(512); if (Tag) { GTag::TextConvertState State(&p); Tag->ConvertToText(State); } Out = p.NewGStr(); } return false; } void GHtml::OnContent(GDocumentEnv::LoadJob *Res) { if (JobSem.Lock(_FL)) { JobSem.Jobs.Add(Res); JobSem.Unlock(); PostEvent(M_JOBS_LOADED); } } GHtmlElement *GHtml::CreateElement(GHtmlElement *Parent) { return new GTag(this, Parent); } bool GHtml::GetVariant(const char *Name, GVariant &Value, 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 GHtml::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. GArray Str; for (const char *c = Cond; *c; ) { if (IsAlpha(*c)) { Str.Add(LgiTokStr(c)); } else if (IsWhiteSpace(*c)) { c++; } else { const char *e = c; while (*e && !IsWhiteSpace(*e) && !IsAlpha(*e)) e++; Str.Add(NewStr(c, e - c)); LgiAssert(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; VScroll->Value(Scr); VScroll->SendNotify(); } else d->OnLoadAnchor.Reset(NewStr(Name)); } } return false; } bool GHtml::GetEmoji() { return d->DecodeEmoji; } void GHtml::SetEmoji(bool i) { d->DecodeEmoji = i; } //////////////////////////////////////////////////////////////////////// class GHtml_Factory : public GViewFactory { GView *NewView(const char *Class, GRect *Pos, const char *Text) { if (_stricmp(Class, "GHtml") == 0) { return new GHtml(-1, 0, 0, 100, 100, new GDefaultDocumentEnv); } return 0; } } GHtml_Factory; ////////////////////////////////////////////////////////////////////// struct BuildContext { GHtmlTableLayout *Layout; GTag *Table; GTag *TBody; GTag *CurTr; GTag *CurTd; int cx, cy; BuildContext() { Layout = NULL; cx = cy = 0; Table = NULL; TBody = NULL; CurTr = NULL; CurTd = NULL; } bool Build(GTag *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) ) { GTag *p = TBody ? TBody : Table; CurTr = new GTag(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 { LgiAssert(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++) { GTag *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; } }; GHtmlTableLayout::GHtmlTableLayout(GTag *table) { Table = table; if (!Table) return; #if 0 BuildContext Ctx; Ctx.Layout = this; Ctx.Build(table, 0); #else int y = 0; GTag *FakeRow = 0; GTag *FakeCell = 0; GTag *r; for (size_t i=0; iChildren.Length(); i++) { r = ToTag(Table->Children[i]); if (r->Display() == GCss::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++) { GTag *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 GTag(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 GTag(Table->Html, FakeRow))) { FakeCell->Tag.Reset(NewStr("td")); FakeCell->TagId = TAG_TD; if ((FakeCell->Cell = new GTag::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 { LgiAssert(FakeCell != NULL); FakeCell->Attach(r); } i = Idx - 1; } } } FakeCell = NULL; for (size_t n=0; nChildren.Length(); n++) { GTag *r = ToTag(Table->Children[n]); if (r->TagId == TAG_TR) { int x = 0; for (size_t i=0; iChildren.Length(); i++) { GTag *cell = ToTag(r->Children[i]); if (!IsTableCell(cell->TagId)) { if (!FakeCell) { // Make a fake TD cell FakeCell = new GTag(Table->Html, NULL); FakeCell->Tag.Reset(NewStr("td")); FakeCell->TagId = TAG_TD; if ((FakeCell->Cell = new GTag::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() == GCss::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 GHtmlTableLayout::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 GHtmlTableLayout::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 GHtmlTableLayout::Set(GTag *t) { if (!t) return false; for (int y=0; yCell->Span.y; y++) { for (int x=0; xCell->Span.x; x++) { // LgiAssert(!c[y][x]); c[t->Cell->Pos.y + y][t->Cell->Pos.x + x] = t; } } return true; } void GTagHit::Dump(const char *Desc) { GArray d, n; GTag *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); } //////////////////////////////////////////////////////////////////////// bool GCssStyle::GetVariant(const char *Name, GVariant &Value, char *Array) { if (!Name) return false; if (!_stricmp(Name, "Display")) // Type: String { Value = Css->ToString(Css->Display()); return Value.Str() != NULL; } else LgiAssert(!"Impl me."); return false; } bool GCssStyle::SetVariant(const char *Name, GVariant &Value, char *Array) { if (!Name) return false; if (!_stricmp(Name, "display")) { const char *d = Value.Str(); if (Css->ParseDisplayType(d)) { GTag *t = dynamic_cast(Css); if (t) { t->Html->Layout(true); t->Html->Invalidate(); } return true; } } else LgiAssert(!"Impl me."); return false; } diff --git a/src/common/Text/GHtmlParser.cpp b/src/common/Text/GHtmlParser.cpp --- a/src/common/Text/GHtmlParser.cpp +++ b/src/common/Text/GHtmlParser.cpp @@ -1,1538 +1,1538 @@ #include "Lgi.h" #include "GDocView.h" #include "GToken.h" #include "GHtmlCommon.h" #include "GHtmlParser.h" #include "GUnicode.h" #define FEATURE_REATTACH_ELEMENTS 1 #define IsBlock(d) ((d) == GCss::DispBlock) char *GHtmlParser::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; } GHtmlElement *GHtmlParser::GetOpenTag(const char *Tag) { if (Tag) { for (int i=(int)OpenTags.Length()-1; i>=0; i--) { GHtmlElement *t = OpenTags[i]; if (t->Tag) { if (_stricmp(t->Tag, Tag) == 0) { return t; } if (_stricmp(t->Tag, "table") == 0) { // stop looking... we don't close tags outside // the table from inside. break; } } } } return 0; } void GHtmlParser::SkipNonDisplay(char *&s) { while (*s) { SkipWhiteSpace(s); if (s[0] == '<' && s[1] == '!' && s[2] == '-' && s[3] == '-') { s += 4; char *e = strstr(s, "-->"); if (e) s = e + 3; else { s += strlen(s); break; } } else break; } } char16 *GHtmlParser::DecodeEntities(const char *s, ssize_t len) { char16 buf[256]; char16 *o = buf; const char *end = s + len; GStringPipe p(256); for (const char *i = s; i < end; ) { if (o - buf > CountOf(buf) - 32) { // We are getting near the end of the buffer... // push existing data into the GStringPipe and // reset the output ptr. p.Write(buf, (o - buf) * sizeof(*o) ); o = buf; } switch (*i) { case '&': { i++; if (*i == '#') { // Unicode Number char n[32] = "", *p = n; char16 Ch; 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++ = *i++; } *p++ = 0; Ch = htoi(n); } else { // Decimal number while (*i && IsDigit(*i) && (p - n) < 31) { *p++ = *i++; } *p++ = 0; Ch = atoi(n); } if (Ch) *o++ = Ch; if (*i && *i != ';') i--; } else { // Named Char const char *e = i; while (*e && IsAlpha(*e) && *e != ';') { e++; } GAutoWString Var(Utf8ToWide(i, e-i)); - uint32 Char = GHtmlStatic::Inst->VarMap.Find(Var); + uint32_t Char = GHtmlStatic::Inst->VarMap.Find(Var); if (Char) { *o++ = Char; i = e; } else { i--; *o++ = *i; } } break; } case '\r': { break; } case ' ': case '\t': case '\n': { *o++ = *i; break; } default: { // Normal char *o++ = *i; break; } } if (*i) i++; else break; } *o = 0; if (p.GetSize() > 0) { // Long string mode... use the GStringPipe p.Write(buf, (o - buf) * sizeof(*o)); return p.NewStrW(); } return NewStrW(buf, o - buf); } char *GHtmlParser::ParsePropValue(char *s, char16 *&Value) { Value = 0; if (s) { if (strchr("\"\'", *s)) { char Delim = *s++; char *Start = s; while (*s && *s != Delim) s++; Value = DecodeEntities(Start, s - Start); if (*s) s++; } else { char *Start = s; while (*s && !IsWhiteSpace(*s) && *s != '>') s++; Value = DecodeEntities(Start, s - Start); } } return s; } char *GHtmlParser::ParseName(char *s, char **Name) { GAutoString a; s = ParseName(s, a); if (Name) *Name = a.Release(); return s; } char *GHtmlParser::ParseName(char *s, GAutoString &Name) { SkipWhiteSpace(s); char *Start = s; while (*s && (IsAlpha(*s) || strchr("!-:\"\'", *s) || IsDigit(*s))) { s++; } ssize_t Len = s - Start; if (Len > 0) { Name.Reset(NewStr(Start, Len)); } return s; } char *GHtmlParser::ParsePropList(char *s, GHtmlElement *Obj, bool &Closed) { while (s && *s) { while (*s && IsWhiteSpace(*s)) s++; if (*s == '/') { Closed = true; s++; } if (*s == '>' || !*s) break; // get name char *Name = 0; char *n = ParseName(s, &Name); if (!n || !*n) break; if (n == s) // Don't get stuck... s = ++n; else s = n; while (*s && IsWhiteSpace(*s)) s++; if (*s == '=') { // get value s++; while (*s && IsWhiteSpace(*s)) s++; char16 *Value = 0; s = ParsePropValue(s, Value); if (Name && Value && *Value) { #if defined(_DEBUG) if (!_stricmp(Name, "debug")) { // int asd=0; } #endif Obj->Set(Name, Value); } DeleteArray(Value); } DeleteArray(Name); while (*s && IsWhiteSpace(*s)) s++; if (!*s || *s == '<') return s; if (*s == '>' || *s == '/') break; } if (*s == '/') s++; if (*s == '>') s++; return s; } GHtmlElemInfo *GHtmlParser::GetTagInfo(const char *Tag) { LgiAssert(GHtmlStatic::Inst != NULL); return GHtmlStatic::Inst->GetTagInfo(Tag); } void DumpDomTree(GHtmlElement *e, int Depth = 0) { char Sp[256]; int d = Depth << 1; memset(Sp, ' ', d); Sp[d] = 0; LgiTrace("%s%s (%p,%p)\n", Sp, e->Tag.Get(), e, e->Parent); for (unsigned i=0; iChildren.Length(); i++) { DumpDomTree(e->Children[i], Depth+1); } } bool GHtmlParser::Parse(GHtmlElement *Root, const char *Doc) { SourceData.Empty(); CurrentSrc = Doc; OpenTags.Length(0); ParseHtml(Root, (char*)Doc, 0); // DumpDomTree(Root); if (CurrentSrc) SourceData.Write(CurrentSrc, strlen(CurrentSrc)); Source.Reset(SourceData.NewStr()); return true; } char *GHtmlParser::ParseHtml(GHtmlElement *Elem, char *Doc, int Depth, bool InPreTag, bool *BackOut) { #if CRASH_TRACE LgiTrace("::ParseHtml Doc='%.10s'\n", Doc); #endif if (Depth >= 1024) { // Bail return Doc + strlen(Doc); } bool IsFirst = true; for (char *s=Doc; s && *s; ) { char *StartTag = s; if (*s == '<') { if (s[1] == '?') { // Dynamic content // Write out the document before the dynamic section if (s > CurrentSrc) { SourceData.Write(CurrentSrc, s - CurrentSrc); } // Process dynamic section s += 2; while (*s && IsWhiteSpace(*s)) s++; if (_strnicmp(s, "xml:namespace", 13) == 0) { // Ignore Outlook generated HTML tag char *e = strchr(s, '/'); while (e) { if (e[1] == '>' || (e[1] == '?' && e[2] == '>')) { if (e[1] == '?') s = e + 3; else s = e + 2; break; } e = strchr(e + 1, '/'); } if (!e) LgiAssert(0); } else { char *Start = s; while (*s && (!(s[0] == '?' && s[1] == '>'))) { if (strchr("\'\"", *s)) { char d = *s++; s = strchr(s, d); if (s) s++; else break; } else s++; } if (s) { if (s[0] == '?' && s[1] == '>' && View && View->GetEnv()) { char *e = s - 1; while (e > Start && IsWhiteSpace(*e)) e--; e++; GAutoString Code(NewStr(Start, e - Start)); if (Code) { GAutoString Result(View->GetEnv()->OnDynamicContent(View, Code)); if (Result) { // Save the dynamic code to the source pipe SourceData.Write(Result, strlen(Result)); // Create some new elements based on the dynamically generated string char *p = Result; do { GHtmlElement *c = CreateElement(Elem); if (c) { p = ParseHtml(c, p, Depth + 1, InPreTag); } else break; } while (ValidStr(p)); } } s += 2; } } } // Move current position to after the dynamic section CurrentSrc = s; } else if (s[1] == '!' && s[2] == '-' && s[3] == '-') { // Comment s = strstr(s, "-->"); if (s) s += 3; } else if (s[1] == '!' && s[2] == '[') { // Parse conditional... char *StartTag = s; s += 3; char *Cond = 0; s = ParseName(s, &Cond); if (!Cond) { while (*s && *s != ']') s++; if (*s == ']') s++; if (*s == '>') s++; return s; } bool IsEndIf = false; if (!_stricmp(Cond, "if")) { if (!IsFirst) { DeleteArray(Cond); s = StartTag; goto DoChildTag; } Elem->TagId = CONDITIONAL; SkipWhiteSpace(s); char *Start = s; while (*s && *s != ']') s++; Elem->Condition.Reset(NewStr(Start, s-Start)); Elem->Tag.Reset(NewStr("[if]")); Elem->Info = GetTagInfo(Elem->Tag); if (!EvaluateCondition(Elem->Condition)) Elem->Display(GCss::DispNone); else Elem->Display(GCss::DispInline); OpenTags.Add(Elem); } else if (!_stricmp(Cond, "endif")) { GHtmlElement *MatchingIf = NULL; for (int i = (int)OpenTags.Length()-1; i>=0; i--) { GHtmlElement *e = OpenTags[i]; if (e->TagId == CONDITIONAL && e->Tag && !_stricmp(e->Tag, "[if]")) { MatchingIf = e; break; } } if (MatchingIf) { MatchingIf->WasClosed = true; IsEndIf = true; OpenTags.Delete(MatchingIf); } } DeleteArray(Cond); while (*s && *s != '>') s++; if (*s == '>') s++; if (IsEndIf) return s; } else if (s[1] == '!') { s += 2; s = strchr(s, '>'); if (s) s++; else return NULL; } else if (IsAlpha(s[1])) { // Start tag if (Elem->Parent && IsFirst) { // My tag s = ParseName(++s, Elem->Tag); if (!Elem->Tag) { if (BackOut) *BackOut = true; return s; } bool TagClosed = false; s = ParsePropList(s, Elem, TagClosed); #if 0 // _DEBUG int Depth = 0; for (GHtmlElement *ep = Elem; ep; ep=ep->Parent) { Depth++; } char Sp[256]; Depth<<=1; memset(Sp, ' ', Depth); Sp[Depth] = 0; LgiTrace("%s%s (this=%p, parent=%p)\n", Sp, Elem->Tag, Elem, Elem->Parent); #endif bool AlreadyOpen = false; Elem->Info = GetTagInfo(Elem->Tag); if (Elem->Info) { if (Elem->Info->Flags & GHtmlElemInfo::TI_SINGLETON) { // Do singleton check... we don't want nested BODY or HEAD tags... for (int i = (int)OpenTags.Length() - 1; i >= 0; i--) { GHtmlElement *e = OpenTags[i]; if (e->TagId == TAG_IFRAME) { // In the case of IFRAMEs... don't consider the parent document. break; } if (e->Tag && !_stricmp(e->Tag, Elem->Tag)) { AlreadyOpen = true; #if 0 // This dumps the tags in the list it = OpenTags.Start(); LgiTrace("Open tags:\n"); for (GHtmlElement *e = *it; e; e = *++it) { GAutoString a = e->DescribeElement(); LgiTrace("\t%s\n", a.Get()); } #endif break; } } } Elem->TagId = Elem->Info->Id; Elem->Display ( TestFlag ( Elem->Info->Flags, GHtmlElemInfo::TI_BLOCK ) || ( Elem->Tag && Elem->Tag[0] == '!' ) ? GCss::DispBlock : GCss::DispInline ); if (Elem->TagId == TAG_PRE) { InPreTag = true; } if (Elem->TagId == TAG_META) { GAutoString Cs; const char *s; if (Elem->Get("http-equiv", s) && _stricmp(s, "Content-Type") == 0) { const char *ContentType; if (Elem->Get("content", ContentType)) { char *CharSet = stristr(ContentType, "charset="); if (CharSet) { char16 *cs = NULL; ParsePropValue(CharSet + 8, cs); Cs.Reset(WideToUtf8(cs)); DeleteArray(cs); } } } if (Elem->Get("name", s) && _stricmp(s, "charset") == 0 && Elem->Get("content", s)) { Cs.Reset(NewStr(s)); } else if (Elem->Get("charset", s)) { Cs.Reset(NewStr(s)); } if (Cs) { if (Cs && _stricmp(Cs, "utf-16") != 0 && _stricmp(Cs, "utf-32") != 0 && LgiGetCsInfo(Cs)) { DocCharSet = Cs; } } } } if (IsBlock(Elem->Display()) || Elem->TagId == TAG_BR) { SkipNonDisplay(s); } Elem->SetStyle(); switch (Elem->TagId) { default: break; case TAG_SCRIPT: { char *End = stristr(s, ""); if (End) { if (View && View->GetEnv()) { *End = 0; GVariant Lang, Type; Elem->GetValue("language", Lang); Elem->GetValue("type", Type); View->GetEnv()->OnCompileScript(View, s, Lang.Str(), Type.Str()); *End = '<'; } else { Elem->Txt.Reset(Utf8ToWide(s, End - s)); } s = End; } break; } case TAG_TABLE: { if (Elem->Parent->TagId == TAG_TABLE) { // Um no... if (BackOut) { GHtmlElement *l = OpenTags.Last(); if (l && l->TagId == TAG_TABLE) { CloseTag(l); } *BackOut = true; return StartTag; } } break; } case TAG_STYLE: { char *End = stristr(s, ""); if (End) { if (View) { GAutoString Css(NewStr(s, End - s)); if (Css) { View->OnAddStyle("text/css", Css); } } s = End; } else { // wtf? return s + strlen(s); } break; } } if (AlreadyOpen || TagClosed || Elem->Info->NeverCloses()) { return s; } #if FEATURE_REATTACH_ELEMENTS if (Elem->Info->Reattach) { GArray ParentTags; switch (Elem->TagId) { case TAG_LI: { ParentTags.Add(TAG_OL); ParentTags.Add(TAG_UL); break; } case TAG_HEAD: { ParentTags.Add(TAG_HTML); break; } case TAG_BODY: { ParentTags.Add(TAG_HTML); break; } case TAG_TBODY: { ParentTags.Add(TAG_TABLE); break; } case TAG_TR: { ParentTags.Add(TAG_TBODY); ParentTags.Add(TAG_TABLE); break; } case TAG_TD: case TAG_TH: { ParentTags.Add(TAG_TR); break; } default: break; } GHtmlElement *p; for (int TagIdx = (int)OpenTags.Length()-1; TagIdx >= 0 && (p = OpenTags[TagIdx]) && p->TagId != TAG_TABLE; TagIdx--) { if (p->TagId == Elem->TagId) { CloseTag(p); break; } } bool Reattach = !ParentTags.HasItem(Elem->Parent->TagId); if (Reattach) { if (Elem->TagId == TAG_HEAD) { // Ignore it.. return s; } else { GHtmlElement *Parent = NULL; for (int TagIdx = (int)OpenTags.Length()-1; TagIdx >= 0; TagIdx--) { GHtmlElement *t = OpenTags[TagIdx]; if (t->TagId && ParentTags.HasItem(t->TagId)) { Parent = t; break; } if (t->TagId == TAG_TABLE) break; } if (Parent) { // Reattach to the right parent. // LgiTrace("Reattaching '%s'(%p) to '%s'(%p) count=%i\n", Elem->Tag, Elem, Parent->Tag, Parent, ++count); Parent->Attach(Elem); } else { // Maybe there is no parent tag? switch (Elem->TagId) { case TAG_TD: case TAG_TH: { // Find a TBODY or TABLE to attach a new ROW GHtmlElement *Attach = Elem->Parent; while (Attach) { if (Attach->TagId == TAG_TABLE || Attach->TagId == TAG_TBODY) break; Attach = Attach->Parent; } if (Attach) { // Create a new ROW GHtmlElement *NewRow = CreateElement(Attach); if (NewRow) { NewRow->Tag.Reset(NewStr("tr")); NewRow->TagId = TAG_TR; NewRow->Info = GetTagInfo(NewRow->Tag); bool IsAttach = Attach->Children.HasItem(NewRow); if (IsAttach) { OpenTags.Add(NewRow); NewRow->Attach(Elem); LgiTrace("Inserted new TAG_TR: %p\n", NewRow); } } else LgiAssert(!"Alloc error"); } else LgiAssert(!"What now?"); break; } default: { // LgiTrace("%s:%i - Warning: '%s' is missing it's parent.\n", _FL, Elem->Tag.Get()); break; } } } } } } #endif OpenTags.Add(Elem); if (Elem->TagId == TAG_IFRAME) { GVariant Src; if (Elem->GetValue("src", Src) && View && View->GetEnv()) { GDocumentEnv::LoadJob *j = View->GetEnv()->NewJob(); if (j) { j->Uri.Reset(Src.ReleaseStr()); j->Env = View->GetEnv(); j->UserData = this; j->UserUid = View ? View->GetDocumentUid() : 0; GDocumentEnv::LoadType Result = View->GetEnv()->GetContent(j); if (Result == GDocumentEnv::LoadImmediate) { GStreamI *s = j->GetStream(); if (s) { uint64 Len = s->GetSize(); if (Len > 0) { GAutoString a(new char[(size_t)Len+1]); ssize_t r = s->Read(a, (int)Len); a[r] = 0; GHtmlElement *Child = CreateElement(Elem); if (Child) { bool BackOut = false; GArray ot = OpenTags; ParseHtml(Child, a, Depth + 1, false, &BackOut); OpenTags = ot; } } } } DeleteObj(j); } } } } else { // Child tag DoChildTag: GHtmlElement *c = CreateElement(Elem); if (c) { bool BackOut = false; s = ParseHtml(c, s, Depth + 1, InPreTag, &BackOut); if (BackOut) { c->Detach(); DeleteObj(c); return s; } else if (IsBlock(c->Display())) { while (c->Children.Length()) { GHtmlElement *Last = c->Children.Last(); if (Last->TagId == CONTENT && !ValidStrW(Last->Txt)) { Last->Detach(); DeleteObj(Last); } else break; } } } } } else if (s[1] == '/') { // End tag char *PreTag = s; s += 2; while (*s == '/') s++; // This code segment detects out of order HTML tags // and skips them. If we didn't do this then the parser // would get stuck on a Tag which has already been closed // and would return to the very top of the recursion. // // e.g. // // // // char *EndBracket = strchr(s, '>'); if (EndBracket) { char *e = EndBracket; while (e > s && strchr(WhiteSpace, e[-1])) e--; GAutoString Name(NewStr(s, e - s)); GHtmlElement *Open = GetOpenTag(Name); if (Open) { Open->WasClosed = true; } else { s = EndBracket + 1; continue; } } else { s += strlen(s); continue; } if (Elem->Tag) { // Compare against our tag char *t = Elem->Tag; while (*s && *t && toupper(*s) == toupper(*t)) { s++; t++; } SkipWhiteSpace(s); if (*s == '>') { GHtmlElement *t; while ((t = OpenTags.Last())) { CloseTag(t); if (t == Elem || OpenTags.Length() == 0) { break; } } s++; if (IsBlock(Elem->Display()) || Elem->TagId == TAG_BR) { SkipNonDisplay(s); } if (Elem->Parent) { return s; } } } else { // Error case happens with borked HTML s = EndBracket + 1; } if (Elem->Parent) { return PreTag; } } else { goto PlainText; } } else if (*s) { // Text child PlainText: char *n = NextTag(s); ssize_t Len = n ? n - s : strlen(s); GAutoWString WStr(CleanText(s, Len, true, InPreTag)); if (WStr && *WStr) { // This loop processes the text into lengths that need different treatment enum TxtClass { TxtNone, TxtEmoji, TxtEol, TxtNull, }; char16 *Start = WStr; GHtmlElement *Child = NULL; for (char16 *c = WStr; true; c++) { TxtClass Cls = TxtNone; /* if (Html->d->DecodeEmoji && *c >= EMOJI_START && *c <= EMOJI_END) Cls = TxtEmoji; else */ if (InPreTag && *c == '\n') Cls = TxtEol; else if (!*c) Cls = TxtNull; if (Cls) { if (c > Start) { // Emit the text before the point of interest... GAutoWString Cur; if (Start == WStr && !*c) { // Whole string Cur = WStr; } else { // Sub-string Cur.Reset(NewStrW(Start, c - Start)); } if (Elem->Children.Length() == 0 && (!Elem->Info || !Elem->Info->NoText()) && !Elem->Txt) { Elem->Txt = Cur; } else if ((Child = CreateElement(Elem))) { Child->Txt = Cur; } } // Now process the text of interest... /* if (Cls == TxtEmoji) { // Emit the emoji image GHtmlElement *img = CreateElement(Elem); if (img) { img->Tag.Reset(NewStr("img")); if ((img->Info = GetTagInfo(img->Tag))) img->TagId = img->Info->Id; GRect rc; EMOJI_CH2LOC(*c, rc); img->Set("src", Html->d->EmojiImg); char css[256]; sprintf_s(css, sizeof(css), "x-rect: rect(%i,%i,%i,%i);", rc.y1, rc.x2, rc.y2, rc.x1); img->Set("style", css); img->SetStyle(); } Start = c + 1; } else */ if (Cls == TxtEol) { // Emit the
tag GHtmlElement *br = CreateElement(Elem); if (br) { br->Tag.Reset(NewStr("br")); if ((br->Info = GetTagInfo(br->Tag))) br->TagId = br->Info->Id; } Start = c + 1; } } // Check for the end of string... if (!*c) break; } } s = n; } IsFirst = false; } #if CRASH_TRACE LgiTrace("::ParseHtml end\n"); #endif return 0; } char16 *GHtmlParser::CleanText(const char *s, ssize_t Len, bool ConversionAllowed, bool KeepWhiteSpace) { static const char *DefaultCs = "iso-8859-1"; char16 *t = 0; if (s && Len > 0) { bool Has8 = false; if (Len >= 0) { for (int n = 0; n < Len; n++) { if (s[n] & 0x80) { Has8 = true; break; } } } else { for (int n = 0; s[n]; n++) { if (s[n] & 0x80) { Has8 = true; break; } } } bool DocAndCsTheSame = false; if (DocCharSet && View && View->GetCharset()) { DocAndCsTheSame = _stricmp(DocCharSet, View->GetCharset()) == 0; } if (!DocAndCsTheSame && DocCharSet && View && View->GetCharset() && !View->GetOverideDocCharset()) { const char *ViewCs = View->GetCharset(); char *DocText = (char*)LgiNewConvertCp(DocCharSet, s, ViewCs, Len); if (DocText) { t = (char16*) LgiNewConvertCp(LGI_WideCharset, DocText, DocCharSet, -1); DeleteArray(DocText); } else { // Can't convert to doc charset? iconv missing? t = Utf8ToWide(s, Len); } } else if (DocCharSet) { t = (char16*) LgiNewConvertCp(LGI_WideCharset, s, DocCharSet, Len); } else { const char *ViewCs = View ? View->GetCharset() : NULL; t = (char16*) LgiNewConvertCp(LGI_WideCharset, s, ViewCs?ViewCs: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++; char16 Ch = 0; 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++; } *p = 0; Ch = htoi(n); } else { // Decimal number while (*i && IsDigit(*i) && (p - n) < 31) { *p++ = (char)*i++; } *p = 0; Ch = atoi(n); } if (Ch) *o++ = Ch; if (*i && *i != ';') i--; } else { // Named Char char16 *e = i; while (*e && IsAlpha(*e) && *e != ';') { e++; } GAutoWString Var(NewStrW(i, e-i)); char16 Char = GHtmlStatic::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; } void GHtmlParser::_TraceOpenTags() { GStringPipe p; for (unsigned i = 0; i < OpenTags.Length(); i++) { GHtmlElement *t = OpenTags[i]; p.Print(", %s", t->Tag.Get()); GVariant Id; if (t->GetValue("id", Id)) { p.Print("#%s", Id.Str()); } } char *s = p.NewStr(); if (s) { LgiTrace("Open tags = '%s'\n", s + 2); DeleteArray(s); } } bool GHtmlParser::ParseColour(const char *s, GCss::ColorDef &c) { if (s) { int m; if (*s == '#') { s++; ParseHexColour: int i = htoi(s); size_t l = strlen(s); if (l == 3) { int r = i >> 8; int g = (i >> 4) & 0xf; int b = i & 0xf; c.Type = GCss::ColorRgb; c.Rgb32 = Rgb32(r | (r<<4), g | (g << 4), b | (b << 4)); } else if (l == 4) { int r = (i >> 12) & 0xf; int g = (i >> 8) & 0xf; int b = (i >> 4) & 0xf; int a = i & 0xf; c.Type = GCss::ColorRgb; c.Rgb32 = Rgba32( r | (r <<4 ), g | (g << 4), b | (b << 4), a | (a << 4)); } else if (l == 6) { c.Type = GCss::ColorRgb; c.Rgb32 = Rgb32(i >> 16, (i >> 8) & 0xff, i & 0xff); } else if (l == 8) { c.Type = GCss::ColorRgb; c.Rgb32 = Rgba32(i >> 24, (i >> 16) & 0xff, (i >> 8) & 0xff, i & 0xff); } else { return false; } return true; } else if ((m = GHtmlStatic::Inst->ColourMap.Find(s)) >= 0) { c.Type = GCss::ColorRgb; c.Rgb32 = Rgb24To32(m); return true; } else if (!_strnicmp(s, "rgb", 3)) { s += 3; SkipWhiteSpace(s); if (*s == '(') { s++; - GArray Col; + GArray Col; while (Col.Length() < 3) { SkipWhiteSpace(s); if (IsDigit(*s)) { Col.Add(atoi(s)); while (*s && IsDigit(*s)) s++; SkipWhiteSpace(s); if (*s == ',') s++; } else break; } SkipWhiteSpace(s); if (*s == ')' && Col.Length() == 3) { c.Type = GCss::ColorRgb; c.Rgb32 = Rgb32(Col[0], Col[1], Col[2]); return true; } } } else if (IsDigit(*s) || (tolower(*s) >= 'a' && tolower(*s) <= 'f')) { goto ParseHexColour; } } return false; } bool GHtmlParser::Is8Bit(char *s) { while (*s) { if (((uchar)*s) & 0x80) return true; s++; } return false; } diff --git a/src/common/Text/GXmlTreeUi.cpp b/src/common/Text/GXmlTreeUi.cpp --- a/src/common/Text/GXmlTreeUi.cpp +++ b/src/common/Text/GXmlTreeUi.cpp @@ -1,426 +1,426 @@ /// \file /// \author Matthew Allen #include "Lgi.h" #include "GXmlTreeUi.h" #include "GCheckBox.h" #include "GButton.h" #include "GRadioGroup.h" #include "GSlider.h" #include "GCombo.h" #include "GEdit.h" #include "GTree.h" #include "GControlTree.h" #include struct Mapping { int Id; int Hint; GXmlTreeUi::CreateListItem ListItemFactory; GXmlTreeUi::CreateTreeItem TreeItemFactory; GVariant ChildElements; void *User; Mapping() { Id = 0; Hint = GV_NULL; ListItemFactory = 0; TreeItemFactory = 0; User = 0; } void LoadTree(GTreeNode *n, GXmlTag *t) { for (GXmlTag *c=t->Children.First(); c; c=t->Children.Next()) { GTreeItem *i = TreeItemFactory(User); if (i) { i->XmlIo(c, false); n->Insert(i); LoadTree(i, c); } } } void SaveTree(GTreeNode *n, GXmlTag *t) { for (GTreeItem *i = n->GetChild(); i; i = i->GetNext()) { GXmlTag *n = new GXmlTag(ChildElements.Str()); if (n) { i->XmlIo(n, true); t->InsertTag(n); SaveTree(i, n); } } } }; class GXmlTreeUiPriv { public: LHashTbl,Mapping*> Maps; ~GXmlTreeUiPriv() { Maps.DeleteObjects(); } }; GXmlTreeUi::GXmlTreeUi() { d = new GXmlTreeUiPriv; } GXmlTreeUi::~GXmlTreeUi() { DeleteObj(d); } void GXmlTreeUi::EmptyAll(GViewI *Ui) { if (Ui) { // for (Mapping *m=d->Maps.First(); m; m=d->Maps.Next()) for (auto m : d->Maps) { if (m.value->Hint == GV_STRING) Ui->SetCtrlName(m.value->Id, 0); } } } void GXmlTreeUi::EnableAll(GViewI *Ui, bool Enable) { if (Ui) { // for (Mapping *m=d->Maps.First(); m; m=d->Maps.Next()) for (auto m : d->Maps) { Ui->SetCtrlEnabled(m.value->Id, Enable); } } } bool GXmlTreeUi::IsMapped(const char *Attr) { return d->Maps.Find(Attr) != NULL; } void GXmlTreeUi::Map(const char *Attr, int UiIdent, int Type) { if (UiIdent > 0 && (Attr != 0 || Type == GV_DOM)) { Mapping *m = new Mapping; if (m) { m->Id = UiIdent; m->Hint = Type; LgiAssert(!d->Maps.Find(Attr)); d->Maps.Add(Attr, m); } } else { LgiAssert(0); } } void GXmlTreeUi::Map(const char *Attr, int UiIdent, CreateListItem Factory, const char *ChildElements, void *User) { if (Attr && UiIdent > 0 && Factory && ChildElements) { Mapping *m = new Mapping; if (m) { m->Id = UiIdent; m->ListItemFactory = Factory; m->Hint = GV_LIST; m->ChildElements = ChildElements; m->User = User; LgiAssert(!d->Maps.Find(Attr)); d->Maps.Add(Attr, m); } } else { LgiAssert(0); } } void GXmlTreeUi::Map(const char *Attr, int UiIdent, CreateTreeItem Factory, const char *ChildElements, void *User) { if (Attr && UiIdent > 0 && Factory && ChildElements) { Mapping *m = new Mapping; if (m) { m->Id = UiIdent; m->TreeItemFactory = Factory; m->Hint = GV_CUSTOM; m->ChildElements = ChildElements; m->User = User; LgiAssert(!d->Maps.Find(Attr)); d->Maps.Add(Attr, m); } } else { LgiAssert(0); } } void GXmlTreeUi::EmptyMaps() { d->Maps.DeleteObjects(); } int GetCtrlType(GViewI *v) { if (v) { if (dynamic_cast(v)) { return GV_DOM; } else if (dynamic_cast(v) || dynamic_cast(v) || dynamic_cast(v)) { return GV_BOOL; } else if (dynamic_cast(v) || dynamic_cast(v) || dynamic_cast(v)) { return GV_INT32; } } return GV_STRING; } int GetDataType(char *str) { if (str) { bool Float = false; char16 w; ssize_t Len = strlen(str); - while ((w = LgiUtf8To32((uint8*&)str, Len))) + while ((w = LgiUtf8To32((uint8_t*&)str, Len))) { if (strchr("e \t\r\n", w)) { // Doesn't really tell us anything, so ignore it. // The 'e' part is sometimes part of a number or // ignore that too. } else if (!IsDigit(w) || w > 255) { return GV_STRING; } else if (w == '.') { Float = true; } } if (Float) { return GV_DOUBLE; } return GV_INT32; } return GV_NULL; } bool GXmlTreeUi::Convert(GDom *Tag, GViewI *Ui, bool ToUI) { bool Status = false; if (Ui && Tag) { GVariant v; GXmlTag *Xml = dynamic_cast(Tag); if (ToUI) { // Xml -> UI // const char *Attr; // for (Mapping *Map = d->Maps.First(&Attr); Map; Map = d->Maps.Next(&Attr)) for (auto Map : d->Maps) { if (Map.value->Hint == GV_LIST) { if (Xml) { GXmlTag *t = Xml->GetChildTag(Map.key); if (!t) continue; LList *Lst; if (!Ui->GetViewById(Map.value->Id, Lst)) continue; Lst->Empty(); for (GXmlTag *c=t->Children.First(); c; c=t->Children.Next()) { LListItem *i = Map.value->ListItemFactory(Map.value->User); if (i) { i->XmlIo(c, false); Lst->Insert(i); } } } } else if (Map.value->Hint == GV_CUSTOM) { if (Xml) { GXmlTag *t = Xml->GetChildTag(Map.key); if (!t) continue; GTree *Tree; if (!Ui->GetViewById(Map.value->Id, Tree)) continue; Tree->Empty(); Map.value->LoadTree(Tree, t); } } else if (Map.value->Hint == GV_DOM) { GControlTree *ct; if (Ui->GetViewById(Map.value->Id, ct)) { ct->Serialize(Xml, false); } } else if (Tag->GetValue(Map.key, v)) { int Type = Map.value->Hint ? Map.value->Hint : GetDataType(v.Str()); if (Type == GV_BOOL || Type == GV_INT32 || Type == GV_INT64) { Ui->SetCtrlValue(Map.value->Id, v.CastInt32()); } else { Ui->SetCtrlName(Map.value->Id, v.Str()); } Status = true; } else { GEdit *c; if (Ui->GetViewById(Map.value->Id, c)) c->Name(""); } } } else { // UI -> Xml // const char *Attr; // for (Mapping *Map = d->Maps.First(&Attr); Map; Map = d->Maps.Next(&Attr)) for (auto Map : d->Maps) { GViewI *c = Ui->FindControl(Map.value->Id); if (c) { int Type = Map.value->Hint ? Map.value->Hint : GetCtrlType(c); switch (Type) { case GV_LIST: { if (!Xml) break; GXmlTag *Child = Xml->GetChildTag(Map.key, true); if (!Child) break; LList *Lst = dynamic_cast(c); if (!Lst) break; Child->Empty(true); Child->SetTag(Map.key); List All; Lst->GetAll(All); for (LListItem *i = All.First(); i; i = All.Next()) { GXmlTag *n = new GXmlTag(Map.value->ChildElements.Str()); if (n) { i->XmlIo(n, true); Child->InsertTag(n); } } break; } case GV_CUSTOM: // GTree { if (!Xml) break; GXmlTag *Child = Xml->GetChildTag(Map.key, true); if (!Child) break; GTree *Tree = dynamic_cast(c); if (!Tree) break; Child->Empty(true); Child->SetTag(Map.key); Map.value->SaveTree(Tree, Child); break; } case GV_INT32: case GV_BOOL: { Tag->SetValue(Map.key, v = c->Value()); Status = true; break; } case GV_DOM: { GControlTree *ct; if (Ui->GetViewById(Map.value->Id, ct)) { ct->Serialize(Xml, true); } break; } default: { char *Str = c->Name(); if (ValidStr(Str)) v = Str; else v.Empty(); Tag->SetValue(Map.key, v); Status = true; break; } } } else { v.Empty(); Tag->SetValue(Map.key, v); } } } } return Status; } diff --git a/src/common/Widgets/GZoomView.cpp b/src/common/Widgets/GZoomView.cpp --- a/src/common/Widgets/GZoomView.cpp +++ b/src/common/Widgets/GZoomView.cpp @@ -1,1833 +1,1833 @@ /* Notes: The scroll bar position is always in document co-ordinates. Not zoomed co-ords. */ #include #include "Lgi.h" #include "GZoomView.h" #include "GScrollBar.h" #include "LThreadEvent.h" #include "GPalette.h" #define MAX_FACTOR 32 #define DEBUG_TILE_BOUNDARIES 0 enum Messages { M_RENDER_FINISHED = M_USER + 800, }; static float ZoomToScale(int Zoom) { if (Zoom < 0) return 1.0f / (1 - Zoom); else if (Zoom > 1) return (float) (Zoom + 1); return 1.0f; } struct ZoomTile : public GMemDC { bool Dirty; GColourSpace MapBits(int Bits) { GColourSpace Cs; #ifdef LINUX Cs = GdcD->GetColourSpace(); #else if (Bits > 32) Bits >>= 1; if (Bits <= 8) Cs = System32BitColourSpace; else Cs = GBitsToColourSpace(Bits); #endif // printf("MapBits = %s\n", GColourSpaceToString(Cs)); return Cs; } ZoomTile(int Size, int Bits) : GMemDC(Size, Size, MapBits(Bits)) { Dirty = true; } }; typedef ZoomTile *SuperTilePtr; class GZoomViewPriv // : public LThread, public LMutex { int Zoom; GAutoPtr TileCache; public: /// If this is true, then we own the pDC object. bool OwnDC; /// The image surface we are displaying GSurface *pDC; /// The parent view that owns us GZoomView *View; /// The callback object... GZoomViewCallback *Callback; /// This is the zoom factor. However: /// -3 = 1/4 (scale down) /// -2 = 1/3 /// -1 = 1/2 /// 0 = exact pixels /// 1 = 2x /// 2 = 3x /// 3 = 4x (scale up) float Scale() { return ZoomToScale(Zoom); } int Factor() { return Zoom < 0 ? 1 - Zoom : Zoom + 1; } /// Number of tiles allocated int TotalTiles; /// Width and height of the tile... int TileSize; /// Size of image in tiles GdcPt2 Tiles; /// The array of tiles. Unused entries will be NULL. ZoomTile ***Tile; // Type of sampling to use GZoomView::SampleMode SampleMode; // Default zooming behaviour GZoomView::DefaultZoomMode DefaultZoom; // Threading stuff enum WorkerMode { ExitNow, Waiting, Active, Stopping, Stopped, } Mode; GZoomViewPriv(GZoomView *view, GZoomViewCallback *callback) { View = view; Callback = callback; Mode = Waiting; Zoom = 0; TileSize = 128; Tiles.x = Tiles.y = 0; Tile = NULL; OwnDC = false; pDC = NULL; SampleMode = GZoomView::SampleNearest; DefaultZoom = GZoomView::ZoomFitBothAxis; } ~GZoomViewPriv() { if (OwnDC) DeleteObj(pDC); EmptyTiles(); } GRect DocToScreen(GRect s) { int f = Factor(); if (Zoom < 0) { // Scaling down (zoomed out) s.x1 /= f; s.y1 /= f; s.x2 = (s.x2 + f - 1) / f; s.y2 = (s.y2 + f - 1) / f; } else if (Zoom > 0) { // Scaling up (zooming in) s.x1 *= f; s.y1 *= f; s.x2 = ((s.x2 + 1) * f) - 1; s.y2 = ((s.y2 + 1) * f) - 1; } return s; } GRect ScreenToDoc(GRect s) { int f = Factor(); if (Zoom > 0) { // Scaling down (zoomed out) s.x1 /= f; s.y1 /= f; s.x2 = (s.x2 + f - 1) / f; s.y2 = (s.y2 + f - 1) / f; } else if (Zoom < 0) { // Scaling up (zooming in) s.x1 *= f; s.y1 *= f; s.x2 *= f; s.y2 *= f; } return s; } GdcPt2 ScreenToDoc(GdcPt2 p) { int f = Factor(); if (GetZoom() > 0) { // Image is scaled up, so divide to get doc coords p.x = (p.x + f - 1) / f; p.y = (p.y + f - 1) / f; } else if (GetZoom() < 0) { // Image is scaled down p.x *= f; p.y *= f; } return p; } GdcPt2 DocToScreen(GdcPt2 p) { int f = Factor(); if (GetZoom() < 0) { // Scaled down p.x = (p.x + f - 1) / f; p.y = (p.y + f - 1) / f; } else if (GetZoom() > 0) { // Scaled up p.x *= f; p.y *= f; } return p; } int GetZoom() { return Zoom; } void SetZoom(int z) { if (Zoom != z) { Zoom = z; EmptyTiles(); } } void SetDefaultZoom() { if (pDC) { GRect c = View->GetClient(); int z; bool Loop = true; for (z=-20; Loop && z<=0; z++) { float s = ZoomToScale(z); int x = (int) (s * pDC->X()); int y = (int) (s * pDC->Y()); switch (DefaultZoom) { case GZoomView::ZoomFitBothAxis: { if (x >= c.X() || y >= c.Y()) { z--; Loop = false; } break; } case GZoomView::ZoomFitX: { if (x >= c.X()) { z--; Loop = false; } break; } case GZoomView::ZoomFitY: { if (y >= c.Y()) { z--; Loop = false; } break; } } } SetZoom(MIN(z, 0)); ResetTiles(); } } void EmptyTiles() { if (Tile) { for (int x=0; xBounds()); // Pick a suitable tile size TileSize = 128; if (Zoom >= 2) { // When scaling up the tile size needs to be // an even multiple of the factor. Otherwise // we have part of a scaled pixel in one tile // and part in another. Which is messy to handle. while (TileSize % Factor()) TileSize++; } Tiles.x = (full.X() + TileSize - 1) / TileSize; Tiles.y = (full.Y() + TileSize - 1) / TileSize; Tile = new SuperTilePtr*[Tiles.x]; for (int x=0; x void ScaleDown24(OutPx *out, InPx *in, int len, int Factor) { if (SampleMode == GZoomView::SampleNearest) { InPx *end = in + len; while (in < end) { out->r = in->r; out->g = in->g; out->b = in->b; in += Factor; out++; } } /* else if (SampleMode == GZoomView::SampleAverage) { System24BitPixel *src[MAX_FACTOR]; for (int i=0; ir; g += src[f]->g; b += src[f]->b; src[f]++; } } dst->r = r / Fsq; dst->g = g / Fsq; dst->b = b / Fsq; dst++; } } */ } template void ScaleDown24To32(OutPx *out, InPx *in, int len, int Factor) { if (SampleMode == GZoomView::SampleNearest) { InPx *end = in + len; while (in < end) { out->r = in->r; out->g = in->g; out->b = in->b; out->a = 0xff; in += Factor; out++; } } } template void ScaleDown24(GColourSpace outCs, void *out, InPx *in, int len, int Factor) { switch (outCs) { #define DownCase(type) \ case Cs##type: \ ScaleDown24((G##type*)out, in, len, Factor); \ break; DownCase(Rgb24) DownCase(Bgr24) DownCase(Rgbx32) DownCase(Bgrx32) DownCase(Xrgb32) DownCase(Xbgr32) #undef DownCase #define DownCase(type) \ case Cs##type: \ ScaleDown24To32((G##type*)out, in, len, Factor); \ break; DownCase(Rgba32) DownCase(Bgra32) DownCase(Argb32) DownCase(Abgr32) #undef DownCase default: LgiAssert(0); break; } } void ScaleDown(GSurface *Dst, GSurface *Src, int Sx, int Sy, int Factor) { // ImageViewTarget *App = View->GetApp(); uchar *DivLut = Div255Lut; Pal32 pal[256]; if (Src->GetColourSpace() == CsIndex8) { GPalette *inPal = Src->Palette(); for (int i=0; i<256; i++) { GdcRGB *rgb = inPal ? (*inPal)[i] : NULL; if (rgb) { pal[i].p32.r = rgb->r; pal[i].p32.g = rgb->g; pal[i].p32.b = rgb->b; } else { pal[i].p32.r = i; pal[i].p32.g = i; pal[i].p32.b = i; } pal[i].p32.a = 255; } #if 0 // def _DEBUG Dst->Colour(GColour(255, 0, 255)); Dst->Rectangle(); #endif } // Now copy the right pixels over using the selected sampling method... for (int y=0; yY(); y++) { int yy = Sy + (y * Factor); if (yy >= Src->Y()) continue; int Ex = Sx + (TileSize * Factor); if (Ex >= Src->X()) Ex = Src->X(); switch (Src->GetColourSpace()) { case CsIndex8: { if (SampleMode == GZoomView::SampleNearest) { - uint8 *src = (*Src)[yy]; - uint8 *end = src + Ex; - uint32 *dst = (uint32*) (*Dst)[y]; + uint8_t *src = (*Src)[yy]; + uint8_t *end = src + Ex; + uint32_t *dst = (uint32_t*) (*Dst)[y]; src += Sx; LgiAssert(Dst->GetColourSpace() == System32BitColourSpace); while (src < end) { *dst++ = pal[*src].u32; src += Factor; } } else if (SampleMode == GZoomView::SampleMax) { - uint8 *s[32]; + uint8_t *s[32]; LgiAssert(Factor < 32); for (int f=0; f val) val = *src; src++; } s[oy] = src; } *dst++ = pal[val].u32; } } else { LgiAssert(!"Impl me."); } break; } case CsRgb15: case CsRgb16: case CsBgr15: case CsBgr16: { uint16 *src = (uint16*) (*Src)[yy]; uint16 *dst = (uint16*) (*Dst)[y]; uint16 *end = src + Ex; src += Sx; if (SampleMode == GZoomView::SampleNearest) { while (src < end) { *dst++ = *src; src += Factor; } } else { LgiAssert(!"Impl me."); } break; } #define ScaleDownCase(type, bits) \ case Cs##type: \ ScaleDown##bits(Dst->GetColourSpace(), (*Dst)[y], (G##type*) (*Src)[yy] + Sx, Ex - Sx, Factor); \ break; ScaleDownCase(Rgbx32, 24); ScaleDownCase(Xrgb32, 24); ScaleDownCase(Bgrx32, 24); ScaleDownCase(Xbgr32, 24); ScaleDownCase(Rgb24, 24); ScaleDownCase(Bgr24, 24); case System32BitColourSpace: { System32BitPixel *src = (System32BitPixel*) (*Src)[yy]; System32BitPixel *dst = (System32BitPixel*) (*Dst)[y]; System32BitPixel *end = src + Ex; src += Sx; #define rop(c) dst->c = (DivLut[DivLut[dst->c * dst->a] * o] + DivLut[src->c * src->a]) * 255 / ra; LgiAssert(Dst->GetColourSpace() == Src->GetColourSpace()); if (SampleMode == GZoomView::SampleNearest) { while (src < end) { if (src->a == 255) { *dst = *src; } else if (src->a) { - uint8 o = 255 - src->a; + uint8_t o = 255 - src->a; int ra = (dst->a + src->a) - DivLut[dst->a * src->a]; rop(r); rop(g); rop(b); dst->a = ra; } dst++; src += Factor; } } else { LgiAssert(!"Impl me."); } #undef rop break; } case CsBgr48: { GBgr48 *src = (GBgr48*) (*Src)[yy]; GBgr24 *dst = (GBgr24*) (*Dst)[y]; GBgr48 *end = src + Ex; src += Sx; LgiAssert(Dst->GetColourSpace() == CsBgr24); if (SampleMode == GZoomView::SampleNearest) { while (src < end) { dst->r = src->r >> 8; dst->g = src->g >> 8; dst->b = src->b >> 8; dst++; src += Factor; } } else { LgiAssert(!"Impl me."); } break; } case CsBgra64: { GBgra64 *s = (GBgra64*) (*Src)[yy]; GBgra32 *d = (GBgra32*) (*Dst)[y]; GBgra64 *end = s + Ex; s += Sx; LgiAssert(Dst->GetColourSpace() == CsBgra32); if (SampleMode == GZoomView::SampleNearest) { while (s < end) { if (s->a == 0xffff) { // Copy pixel d->r = s->r >> 8; d->g = s->g >> 8; d->b = s->b >> 8; d->a = s->a >> 8; } else if (s->a) { // Composite with dest - uint8 o = (0xffff - s->a) >> 8; - uint8 dc, sc; + uint8_t o = (0xffff - s->a) >> 8; + uint8_t dc, sc; Rgb16to8PreMul(r); Rgb16to8PreMul(g); Rgb16to8PreMul(b); } d++; s += Factor; } } else { LgiAssert(!"Impl me."); } break; } default: { LgiAssert(!"Not impl"); break; } } } } /// Scale pixels up into a tile void ScaleUp ( /// The tile to write to GSurface *Dst, /// The source bitmap we are displaying GSurface *Src, /// X offset into source for tile's data int Sx, /// Y offset into source int Sy, /// The scaling factor to apply int Factor ) { COLOUR Dark = GdcD->GetColour(Rgb24(128, 128, 128), Dst); COLOUR Light = GdcD->GetColour(Rgb24(192, 192, 192), Dst); int f = Factor - 1; int HasGrid = Callback ? Callback->DisplayGrid() : false; int HasTile = Callback ? Callback->DisplayTile() : false; // Now copy the right pixels over... int Pix = TileSize / Factor; LgiAssert(Dst->X() % Factor == 0); Pal32 pal[256]; switch (Src->GetColourSpace()) { default: { LgiAssert(0); break; } case CsIndex8: { GPalette *inPal = Src->Palette(); for (int i=0; i<256; i++) { GdcRGB *rgb = inPal ? (*inPal)[i] : NULL; if (rgb) { pal[i].p32.r = rgb->r; pal[i].p32.g = rgb->g; pal[i].p32.b = rgb->b; } else { pal[i].p32.r = i; pal[i].p32.g = i; pal[i].p32.b = i; } pal[i].p32.a = 255; } break; } case System16BitColourSpace: case System24BitColourSpace: case System32BitColourSpace: { if (Callback && (!TileCache || TileCache->X() != TileSize || TileCache->Y() != TileSize)) { TileCache.Reset(new GMemDC(TileSize, TileSize, System32BitColourSpace)); } if (TileCache) { GRect s; s.ZOff(TileSize-1, TileSize-1); s.Offset(Sx, Sy); GdcPt2 off(Sx, Sy); Callback->DrawBackground(View, TileCache, off, NULL); TileCache->Op(GDC_ALPHA); TileCache->Blt(0, 0, Src, &s); } break; } } for (int y=0; y= Src->Y()) continue; int EndX = Sx + Pix; if (EndX > Src->X()) EndX = Src->X(); switch (Src->GetColourSpace()) { case CsIndex8: { - uint8 *src = (*Src)[SrcY]; - uint8 *end = src + EndX; + uint8_t *src = (*Src)[SrcY]; + uint8_t *end = src + EndX; src += Sx; while (src < end) { Dst->Colour(pal[*src++].u32, 32); Dst->Rectangle(DstX, DstY, DstX+f, DstY+f); DstX += Factor; } break; } case CsRgb15: case CsRgb16: case CsBgr15: case CsBgr16: { uint16 *src = (uint16*) (*Src)[SrcY]; uint16 *end = src + EndX; src += Sx; while (src < end) { Dst->Colour(*src++); Dst->Rectangle(DstX, DstY, DstX+f, DstY+f); DstX += Factor; } break; } case System24BitColourSpace: { System24BitPixel *src = ((System24BitPixel*) (*Src)[SrcY]); System24BitPixel *end = src + EndX; src += Sx; while (src < end) { Dst->Colour(Rgb24(src->r, src->g, src->b)); Dst->Rectangle(DstX, DstY, DstX+f, DstY+f); DstX += Factor; src++; } break; } case CsBgr48: { GBgr48 *src = ((GBgr48*) (*Src)[SrcY]); GBgr48 *end = src + EndX; src += Sx; while (src < end) { Dst->Colour(Rgb24(src->r >> 8, src->g >> 8, src->b >> 8)); Dst->Rectangle(DstX, DstY, DstX+f, DstY+f); DstX += Factor; src++; } break; } case CsRgb48: { GRgb48 *src = ((GRgb48*) (*Src)[SrcY]); GRgb48 *end = src + EndX; src += Sx; while (src < end) { Dst->Colour(Rgb24(src->r >> 8, src->g >> 8, src->b >> 8)); Dst->Rectangle(DstX, DstY, DstX+f, DstY+f); DstX += Factor; src++; } break; } case CsRgba64: { GRgba64 *src = ((GRgba64*) (*Src)[SrcY]); GRgba64 *end = src + EndX; src += Sx; Dst->Op(GDC_ALPHA); while (src < end) { Dst->Colour(Rgba32(src->r >> 8, src->g >> 8, src->b >> 8, src->a >> 8)); Dst->Rectangle(DstX, DstY, DstX+f, DstY+f); DstX += Factor; src++; } break; } case CsBgra64: { GBgra64 *src = ((GBgra64*) (*Src)[SrcY]); GBgra64 *end = src + EndX; src += Sx; Dst->Op(GDC_ALPHA); while (src < end) { Dst->Colour(Rgba32(src->r >> 8, src->g >> 8, src->b >> 8, src->a >> 8)); Dst->Rectangle(DstX, DstY, DstX+f, DstY+f); DstX += Factor; src++; } break; } case System32BitColourSpace: { System32BitPixel *src, *end; if (TileCache) { src = (System32BitPixel*) (*TileCache)[y]; end = src + (EndX - Sx); } else { src = (System32BitPixel*) (*Src)[SrcY]; end = src + EndX; src += Sx; } if (HasGrid) { - uint32 *s = (uint32*)src; - uint32 *e = (uint32*)end; + uint32_t *s = (uint32_t*)src; + uint32_t *e = (uint32_t*)end; if (Factor == 2) { - uint32 *dst0 = (uint32*) (*Dst)[DstY]; - uint32 *dst1 = (uint32*) (*Dst)[DstY+1]; + uint32_t *dst0 = (uint32_t*) (*Dst)[DstY]; + uint32_t *dst1 = (uint32_t*) (*Dst)[DstY+1]; LgiAssert(Src->GetColourSpace() == Dst->GetColourSpace()); if (dst1) { while (s < e) { *dst0++ = *s; *dst0++ = *s; *dst1++ = *s++; *dst1++ = Light; } } else if (dst0) { while (s < e) { *dst0++ = *s; *dst0++ = *s++; } } } else if (Factor == 3) { - uint32 *dst0 = (uint32*) (*Dst)[DstY]; - uint32 *dst1 = (uint32*) (*Dst)[DstY+1]; - uint32 *dst2 = (uint32*) (*Dst)[DstY+2]; + uint32_t *dst0 = (uint32_t*) (*Dst)[DstY]; + uint32_t *dst1 = (uint32_t*) (*Dst)[DstY+1]; + uint32_t *dst2 = (uint32_t*) (*Dst)[DstY+2]; LgiAssert(Src->GetColourSpace() == Dst->GetColourSpace()); if (dst2) { while (s < e) { *dst0++ = *s; *dst0++ = *s; *dst0++ = *s; *dst1++ = *s; *dst1++ = *s; *dst1++ = Light; *dst2++ = *s++; *dst2++ = Light; *dst2++ = Dark; } } else if (dst1) { while (s < e) { *dst0++ = *s; *dst0++ = *s; *dst0++ = *s; *dst1++ = *s; *dst1++ = *s++; *dst1++ = Light; } } else if (dst0) { while (s < e) { *dst0++ = *s; *dst0++ = *s; *dst0++ = *s++; } } } else { // General case int ff = f - 1; while (src < end) { Dst->Colour(Rgba32(src->r, src->g, src->b, src->a)); Dst->Rectangle(DstX, DstY, DstX+ff, DstY+ff); DstX += Factor; src++; } ((GMemDC*)Dst)->HorzLine(0, Dst->X(), DstY+f, Light, Dark); } } else { while (src < end) { Dst->Colour(Rgba32(src->r, src->g, src->b, src->a)); Dst->Rectangle(DstX, DstY, DstX+f, DstY+f); DstX += Factor; src++; } } break; } default: { LgiAssert(!"Not impl"); break; } } } GMemDC *Mem = dynamic_cast(Dst); if (Mem && Factor > 3) { if (HasGrid) { for (int x = Factor - 1; xX(); x += Factor) { Mem->VertLine(x, 0, Dst->Y(), Light, Dark); } for (int y = Factor - 1; yY(); y += Factor) { Mem->HorzLine(0, Dst->X(), y, Light, Dark); } } if (HasTile) { COLOUR DarkBlue = GdcD->GetColour(Rgb24(0, 0, 128), Dst); COLOUR LightBlue = GdcD->GetColour(Rgb24(0, 0, 255), Dst); int TileX = 16; int TileY = 16; if (Callback) { switch (Callback->TileType()) { case 0: { TileX = TileY = 16; break; } case 1: { TileX = TileY = 32; break; } default: { TileX = Callback->TileX(); TileY = Callback->TileY(); break; } } } int i = TileX - (Sx % TileX); for (int x = i * Factor - 1; xX(); x += Factor * TileX) { Mem->VertLine(x, 0, Dst->Y(), LightBlue, DarkBlue); } i = TileY - (Sy % TileY); for (int y = i * Factor - 1; yY(); y += Factor * TileY) { Mem->HorzLine(0, Dst->X(), y, LightBlue, DarkBlue); } } } } void UpdateTile(int x, int y) { LgiAssert(x >= 0 && x < Tiles.x); LgiAssert(y >= 0 && y < Tiles.y); ZoomTile *Dst = Tile[x][y]; GSurface *Src = pDC; if (Dst && Src) { // Draw background if (Callback) { GRect r = Dst->Bounds(); GdcPt2 off(x, y); Callback->DrawBackground(View, Dst, off, &r); } else { Dst->Colour(LC_WORKSPACE, 24); Dst->Rectangle(); } int f = Factor(); if (Zoom < 0) { ScaleDown( Dst, Src, (x * TileSize) * f, (y * TileSize) * f, f); } else if (Zoom > 0) { ScaleUp( Dst, Src, (x * TileSize) / f, (y * TileSize) / f, f); } else { // 1:1 GRect s; s.ZOff(TileSize-1, TileSize-1); s.Offset(x * TileSize, y * TileSize); Dst->Op(GDC_ALPHA); Dst->Blt(0, 0, Src, &s); } // Draw any foreground elements if (Callback) { GRect r = Dst->Bounds(); GdcPt2 off(x, y); Callback->DrawForeground(View, Dst, off, &r); } Dst->Dirty = false; } } }; GZoomView::GZoomView(GZoomViewCallback *callback) : ResObject(Res_Custom) { d = new GZoomViewPriv(this, callback); Sunken(true); } GZoomView::~GZoomView() { DeleteObj(d); } bool GZoomView::OnLayout(GViewLayoutInfo &Inf) { Inf.Width.Min = -1; Inf.Width.Max = -1; Inf.Height.Min = -1; Inf.Height.Max = -1; return true; } void GZoomView::UpdateScrollBars(GdcPt2 *MaxScroll, bool ResetPos) { GSurface *Src = d->pDC; if (!Src) { SetScrollBars(false, false); return; } GRect c = GetClient(); // int Factor = d->Factor(); // int Fmin1 = Factor - 1; GdcPt2 DocSize(Src->X(), Src->Y()); GdcPt2 DocClientSize(c.X(), c.Y()); DocClientSize = d->ScreenToDoc(DocClientSize); SetScrollBars(DocSize.x > DocClientSize.x, DocSize.y > DocClientSize.y); if (HScroll) { HScroll->SetLimits(0, DocSize.x); HScroll->SetPage(DocClientSize.x); if (ResetPos) HScroll->Value(0); } if (VScroll) { VScroll->SetLimits(0, DocSize.y); VScroll->SetPage(DocClientSize.y); if (ResetPos) VScroll->Value(0); } if (MaxScroll) { MaxScroll->x = DocSize.x - DocClientSize.x; MaxScroll->y = DocSize.y - DocClientSize.y; } } void GZoomView::OnPosChange() { UpdateScrollBars(); } void GZoomView::SetSurface(GSurface *dc, bool own) { if (d->OwnDC) DeleteObj(d->pDC); d->pDC = dc; d->OwnDC = own; Reset(); } GSurface *GZoomView::GetSurface() { return d->pDC; } void GZoomView::Update(GRect *Where) { #if DEBUG_THREADING if (!d->Dirty) LgiTrace("%i setting DIRTY\n", LgiGetCurrentThread()); #endif GRect w; if (Where) { // Some tiles only w = d->DocToScreen(*Where); w.x1 /= d->TileSize; w.y1 /= d->TileSize; w.x2 /= d->TileSize; w.y2 /= d->TileSize; GRect b(0, 0, d->Tiles.x-1, d->Tiles.y-1); w.Bound(&b); } else { // All tiles w.ZOff(d->Tiles.x-1, d->Tiles.y-1); } if (d->Tile && w.Valid()) { for (int y=w.y1; y<=w.y2; y++) { for (int x=w.x1; x<=w.x2; x++) { if (d->Tile[x][y]) { d->Tile[x][y]->Dirty = true; } } } } Invalidate(); } void GZoomView::Reset() { d->EmptyTiles(); d->SetDefaultZoom(); UpdateScrollBars(NULL, true); Invalidate(); } int GZoomView::GetBlockSize() { return d->GetZoom() > 0 ? 16 : 1; } GZoomView::ViewportInfo GZoomView::GetViewport() { ViewportInfo v; v.Zoom = d->GetZoom(); v.Sy = VScroll ? (int)VScroll->Value() : 0; v.Sx = HScroll ? (int)HScroll->Value() : 0; v.TilePx = d->TileSize; return v; } void GZoomView::SetDefaultZoomMode(DefaultZoomMode m) { d->DefaultZoom = m; } GZoomView::DefaultZoomMode GZoomView::GetDefaultZoomMode() { return d->DefaultZoom; } void GZoomView::ScrollToPoint(GdcPt2 DocCoord) { if (!d->pDC) return; if (HScroll) { int64 DocX = d->pDC->X(); int64 Page = HScroll->Page(); int64 MaxVal = DocX - (Page - 1); int64 x1 = HScroll->Value(); int64 x2 = x1 + Page; if (DocCoord.x < x1) HScroll->Value(MAX(DocCoord.x, 0)); else if (DocCoord.x > x2) HScroll->Value(MIN(DocCoord.x-Page+1, MaxVal)); } if (VScroll) { int DocY = d->pDC->Y(); int Page = (int)VScroll->Page(); int MaxVal = DocY - (Page - 1); int64 y1 = VScroll->Value(); int64 y2 = y1 + Page; if (DocCoord.y < y1) VScroll->Value(MAX(DocCoord.y, 0)); else if (DocCoord.y > y2) VScroll->Value(MIN(DocCoord.y-Page+1, MaxVal)); } } void GZoomView::SetSampleMode(SampleMode sm) { d->SampleMode = sm; } void GZoomView::SetViewport(ViewportInfo i) { d->SetZoom(i.Zoom); if (!d->Tile) d->ResetTiles(); GSurface *Src = d->pDC; if (Src) { GdcPt2 MaxScroll; UpdateScrollBars(&MaxScroll); if (HScroll) { if (i.Sx < 0) i.Sx = 0; if (i.Sx > MaxScroll.x) i.Sx = MaxScroll.x; HScroll->Value(i.Sx); } if (VScroll) { if (i.Sy < 0) i.Sy = 0; if (i.Sy > MaxScroll.y) i.Sy = MaxScroll.y; VScroll->Value(i.Sy); } // FIXME... mark stuff dirty Invalidate(); } } void GZoomView::SetCallback(GZoomViewCallback *cb) { d->Callback = cb; } bool GZoomView::Convert(GPointF &p, int x, int y) { int Sx = 0, Sy = 0; int Factor = d->Factor(); GetScrollPos(Sx, Sy); if (d->GetZoom() > 0) { // Scaled up p.x = (double)Sx + ((double)x / Factor); p.y = (double)Sy + ((double)y / Factor); } else if (d->GetZoom() < 0) { // Scaled down p.x = Sx + (x * Factor); p.y = Sy + (y * Factor); } else { p.x = Sx + x; p.y = Sy + y; } GSurface *Src = d->pDC; if (Src && p.x >= 0 && p.x <= Src->X() && p.y >= 0 && p.y <= Src->Y()) { return true; } return false; } void GZoomView::OnMouseClick(GMouse &m) { if (m.Down()) Focus(true); } bool GZoomView::OnMouseWheel(double Lines) { GMouse m; GetMouse(m); if (m.Ctrl()) { GPointF DocPt; bool In = Convert(DocPt, m.x, m.y); // Zoom the graphic if (Lines < 0) { d->SetZoom(d->GetZoom() + 1); } else if (Lines > 0) { d->SetZoom(d->GetZoom() - 1); } else return true; d->ResetTiles(); if (d->Callback) { char Msg[256]; sprintf_s( Msg, sizeof(Msg), "Zoom: %s%i", d->GetZoom() < 0 ? "1/" : (d->GetZoom() == 0 ? "1:" : ""), d->Factor()); d->Callback->SetStatusText(Msg); } // Is the cursor over the image??? if (In) { GSurface *Src = d->pDC; if (Src) { GRect c = GetClient(); int Factor = d->Factor(); int NewSx, NewSy; if (d->GetZoom() > 0) { // Scale up NewSx = (int) (DocPt.x - (m.x / Factor)); NewSy = (int) (DocPt.y - (m.y / Factor)); } else if (d->GetZoom() < 0) { // Scale down NewSx = (int)DocPt.x - (m.x * Factor); NewSy = (int)DocPt.y - (m.y * Factor); } else { // 1:1 NewSx = (int) (DocPt.x - m.x); NewSy = (int) (DocPt.y - m.y); } GdcPt2 ScaledDocSize(Src->X(), Src->Y()); ScaledDocSize = d->DocToScreen(ScaledDocSize); SetScrollBars(ScaledDocSize.x > c.X(), ScaledDocSize.y > c.Y()); /* GdcPt2 MaxScroll( HScroll ? ScaledDocSize.x - c.X() : 0, VScroll ? ScaledDocSize.y - c.Y() : 0); MaxScroll = d->ScreenToDoc(MaxScroll); if (NewSx > MaxScroll.x) NewSx = MaxScroll.x; if (NewSy > MaxScroll.y) NewSy = MaxScroll.y; */ GdcPt2 ScaledClient(c.X(), c.Y()); ScaledClient = d->ScreenToDoc(ScaledClient); if (HScroll) { HScroll->SetLimits(0, Src->X()); HScroll->SetPage(ScaledClient.x); HScroll->Value(NewSx); } if (VScroll) { VScroll->SetLimits(0, Src->Y()); VScroll->SetPage(ScaledClient.y); VScroll->Value(NewSy); } #if 0 { GPointF NewDocPt; bool In = Convert(NewDocPt, m.x, m.y); /* LgiTrace("Scroll: doc=%i,%i cli=%i,%i maxx=%i maxy=%i\n", Src->X(), Src->Y(), c.X(), c.Y(), MaxScroll.x, MaxScroll.y); */ LgiTrace("Zoom: DocPt=%.2f,%.2f->%.2f,%.2f Mouse=%i,%i Factor: %i Zoom: %i DocSize: %i,%i NewScroll: %i,%i\n", DocPt.x, DocPt.y, NewDocPt.x, NewDocPt.y, m.x, m.y, Factor, d->GetZoom(), Src->X(), Src->Y(), NewSx, NewSy); } #endif } } // Update the screen // FIXME... mark stuff dirty Invalidate(); SendNotify(GNotifyViewport_Changed); } else { // Normal wheel if (m.Shift()) { // Scroll in the X direction... if (HScroll) { HScroll->Value((int64) (HScroll->Value() + (Lines * 5))); SendNotify(GNotifyViewport_Changed); } } else { // Scroll in the Y direction... if (VScroll) { VScroll->Value((int64) (VScroll->Value() + (Lines * 5))); SendNotify(GNotifyViewport_Changed); } } } return true; } void GZoomView::OnPulse() { GMouse m; if (!GetMouse(m)) { LgiTrace("%s:%i - GetMouse failed.\n", _FL); return; } GRect c = GetClient(); bool Inside = c.Overlap(m.x, m.y); if (!Inside) { float s = ZoomToScale(d->GetZoom()); // Scroll window to show pixel under the mouse... bool Update = false; if (VScroll) { int Amount = 0; if (m.y < 0) Amount = (int) (m.y / s); else if (m.y > c.y2) Amount = (int) ((m.y - c.y2) / s); if (Amount) { VScroll->Value(VScroll->Value() + Amount); SendNotify(GNotifyViewport_Changed); Update = true; } } if (HScroll) { int Amount = 0; if (m.x < 0) Amount = (int) (m.x / s); else if (m.x > c.x2) Amount = (int) ((m.x - c.x2) / s); if (Amount) { HScroll->Value(HScroll->Value() + Amount); SendNotify(GNotifyViewport_Changed); Update = true; } } if (Update) { OnMouseMove(m); } } } GMessage::Param GZoomView::OnEvent(GMessage *m) { switch (MsgCode(m)) { case M_RENDER_FINISHED: { return 0; } } return GLayout::OnEvent(m); } int GZoomView::OnNotify(GViewI *v, int f) { switch (v->GetId()) { case IDC_VSCROLL: case IDC_HSCROLL: { Invalidate(); #if WINNATIVE UpdateWindow(Handle()); #endif SendNotify(GNotifyViewport_Changed); break; } } return 0; } void GZoomView::OnPaint(GSurface *pDC) { #if 0 // coverage test pDC->Colour(GColour(255, 0, 255)); pDC->Rectangle(); #endif GRect c = GetClient(); GRegion Rgn(c); GSurface *Src = d->pDC; if (Src) { if (!d->Tile) d->ResetTiles(); int Sx = 0, Sy = 0; GetScrollPos(Sx, Sy); // Get the image bounds and scroll it into position (view coords) GRect ScaledDocSize = Src->Bounds(); ScaledDocSize = d->DocToScreen(ScaledDocSize); GRect s = ScaledDocSize; // Scroll positions are in doc px, so scale them here to screen GdcPt2 ScaledScroll(Sx, Sy); ScaledScroll = d->DocToScreen(ScaledScroll); s.Offset(-ScaledScroll.x, -ScaledScroll.y); // Work out the visible tiles... GRect vis = s; vis.Bound(&c); vis.Offset(ScaledScroll.x, ScaledScroll.y); GRect tile; tile.x1 = vis.x1 / d->TileSize; tile.y1 = vis.y1 / d->TileSize; tile.x2 = vis.x2 / d->TileSize; tile.y2 = vis.y2 / d->TileSize; // Check the visible tiles are in range LgiAssert(tile.x1 >= 0 && tile.x1 < d->Tiles.x); LgiAssert(tile.y1 >= 0 && tile.y1 < d->Tiles.y); LgiAssert(tile.x2 >= 0 && tile.x2 < d->Tiles.x); LgiAssert(tile.y2 >= 0 && tile.y2 < d->Tiles.y); // Make sure we have tiles available and they are up to date int Bits = Src->GetBits() <= 8 ? 32 : Src->GetBits(); for (int y=tile.y1; y<=tile.y2; y++) { for (int x=tile.x1; x<=tile.x2; x++) { if (!d->Tile[x][y] || d->Tile[x][y]->GetBits() != Bits) { DeleteObj(d->Tile[x][y]); d->Tile[x][y] = new ZoomTile(d->TileSize, Bits); } if (d->Tile[x][y] && d->Tile[x][y]->Dirty) { d->UpdateTile(x, y); } } } // Paint our tiles... pDC->SetOrigin(ScaledScroll.x, ScaledScroll.y); for (int y=tile.y1; y<=tile.y2; y++) { bool LastY = y == d->Tiles.y - 1; for (int x=tile.x1; x<=tile.x2; x++) { if (d->Tile[x][y]) { int px = x * d->TileSize; int py = y * d->TileSize; GRect r(px, py, px + d->TileSize - 1, py + d->TileSize - 1); if (LastY || x == d->Tiles.x - 1) { // We have to clip off the part of the tile that doesn't // contain image. // Work out how much image is in the tile GRect img = r; if (img.x2 >= ScaledDocSize.X()) img.x2 = ScaledDocSize.X(); if (img.y2 >= ScaledDocSize.Y()) img.y2 = ScaledDocSize.Y(); // Work out the image pixels in tile co-ords GRect tile_source(0, 0, img.X()-1, img.Y()-1); // Also work out where this is on the screen GRect screen_source = tile_source; screen_source.Offset(px, py); // Blt the tile image pixels to the screen GSurface *pTile = d->Tile[x][y]; pDC->Blt(px, py, pTile, &tile_source); /* printf("Zoom: %s->%s (%s)\n", GColourSpaceToString(pTile->GetColourSpace()), GColourSpaceToString(pDC->GetColourSpace()), GColourSpaceToString(GdcD->GetColourSpace())); */ #if DEBUG_TILE_BOUNDARIES pDC->Colour(GColour(0, 0, 255)); pDC->Box(&screen_source); #endif screen_source.Offset(-ScaledScroll.x, -ScaledScroll.y); Rgn.Subtract(&screen_source); } else { // Full tile of image data pDC->Blt(px, py, d->Tile[x][y]); #if DEBUG_TILE_BOUNDARIES pDC->Colour(GColour(64, 0, 255)); pDC->Box(&r); #endif r.Offset(-ScaledScroll.x, -ScaledScroll.y); Rgn.Subtract(&r); } } else LgiAssert(!"Missing tile?"); } } } pDC->SetOrigin(0, 0); for (GRect *r = Rgn.First(); r; r = Rgn.Next()) { pDC->Colour(LC_MED, 24); pDC->Rectangle(r); #if 0 pDC->Colour(GColour(255, 0, 255)); pDC->Box(r); #endif } } class GZoomViewFactory : public GViewFactory { public: GView *NewView(const char *Class, GRect *Pos, const char *Text) { if (!_stricmp(Class, "GZoomView")) return new GZoomView(NULL); return NULL; } } ZoomViewFactory; \ No newline at end of file