/* * Copyright (C) 2003-2006 Gabest * http://www.gabest.org * * This Program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This Program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Make; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * http://www.gnu.org/copyleft/gpl.html * * TODO: do something about bidi (at least handle these ranges: 0590-07BF, FB1D-FDFF, FE70-FEFF) * */ #include "stdafx.h" #include "Renderer.h" #include "Arabic.h" namespace ssf { template void ReverseList(T& l) { POSITION pos = l.GetHeadPosition(); while(pos) { POSITION cur = pos; l.GetNext(pos); l.AddHead(l.GetAt(cur)); l.RemoveAt(cur); } } static CPoint GetAlignPoint(const Placement& placement, const Size& scale, const CRect& frame, const CSize& size) { CPoint p; p.x = frame.left; p.x += placement.pos.auto_x ? placement.align.h * (frame.Width() - size.cx) : placement.pos.x * scale.cx - placement.align.h * size.cx; p.y = frame.top; p.y += placement.pos.auto_y ? placement.align.v * (frame.Height() - size.cy) : placement.pos.y * scale.cy - placement.align.v * size.cy; return p; } static CPoint GetAlignPoint(const Placement& placement, const Size& scale, const CRect& frame) { CSize size(0, 0); return GetAlignPoint(placement, scale, frame, size); } // Renderer::Renderer() { m_hDC = CreateCompatibleDC(NULL); SetBkMode(m_hDC, TRANSPARENT); SetTextColor(m_hDC, 0xffffff); SetMapMode(m_hDC, MM_TEXT); } Renderer::~Renderer() { DeleteDC(m_hDC); } void Renderer::NextSegment(const CAutoPtrList& subs) { StringMapW names; POSITION pos = subs.GetHeadPosition(); while(pos) names[subs.GetNext(pos)->m_name] = true; pos = m_sra.GetStartPosition(); while(pos) { POSITION cur = pos; const CStringW& name = m_sra.GetNextKey(pos); if(!names.Lookup(name)) m_sra.RemoveAtPos(cur); } } RenderedSubtitle* Renderer::Lookup(const Subtitle* s, const CSize& vs, const CRect& vr) { m_sra.UpdateTarget(vs, vr); if(s->m_text.IsEmpty()) return NULL; CRect spdrc = s->m_frame.reference == _T("video") ? vr : CRect(CPoint(0, 0), vs); if(spdrc.IsRectEmpty()) return NULL; RenderedSubtitle* rs = NULL; if(m_rsc.Lookup(s->m_name, rs)) { if(!s->m_animated && rs->m_spdrc == spdrc) return rs; m_rsc.Invalidate(s->m_name); } const Style& style = s->m_text.GetHead().style; Size scale; scale.cx = (float)spdrc.Width() / s->m_frame.resolution.cx; scale.cy = (float)spdrc.Height() / s->m_frame.resolution.cy; CRect frame; frame.left = (int)(64.0f * (spdrc.left + style.placement.margin.l * scale.cx) + 0.5); frame.top = (int)(64.0f * (spdrc.top + style.placement.margin.t * scale.cy) + 0.5); frame.right = (int)(64.0f * (spdrc.right - style.placement.margin.r * scale.cx) + 0.5); frame.bottom = (int)(64.0f * (spdrc.bottom - style.placement.margin.b * scale.cy) + 0.5); CRect clip; if(style.placement.clip.l == -1) clip.left = 0; else clip.left = (int)(spdrc.left + style.placement.clip.l * scale.cx); if(style.placement.clip.t == -1) clip.top = 0; else clip.top = (int)(spdrc.top + style.placement.clip.t * scale.cy); if(style.placement.clip.r == -1) clip.right = vs.cx; else clip.right = (int)(spdrc.left + style.placement.clip.r * scale.cx); if(style.placement.clip.b == -1) clip.bottom = vs.cy; else clip.bottom = (int)(spdrc.top + style.placement.clip.b * scale.cy); clip.left = max(clip.left, 0); clip.top = max(clip.top, 0); clip.right = min(clip.right, vs.cx); clip.bottom = min(clip.bottom, vs.cy); scale.cx *= 64; scale.cy *= 64; bool vertical = s->m_direction.primary == _T("down") || s->m_direction.primary == _T("up"); // create glyph paths WCHAR c_prev = 0, c_next; CAutoPtrList glyphs; POSITION pos = s->m_text.GetHeadPosition(); while(pos) { const Text& t = s->m_text.GetNext(pos); LOGFONT lf; memset(&lf, 0, sizeof(lf)); lf.lfCharSet = DEFAULT_CHARSET; _tcscpy_s(lf.lfFaceName, CString(t.style.font.face)); lf.lfHeight = (LONG)(t.style.font.size * scale.cy + 0.5); lf.lfWeight = (LONG)(t.style.font.weight + 0.5); lf.lfItalic = !!t.style.font.italic; lf.lfUnderline = !!t.style.font.underline; lf.lfStrikeOut = !!t.style.font.strikethrough; lf.lfOutPrecision = OUT_TT_PRECIS; lf.lfClipPrecision = CLIP_DEFAULT_PRECIS; lf.lfQuality = ANTIALIASED_QUALITY; lf.lfPitchAndFamily = DEFAULT_PITCH|FF_DONTCARE; FontWrapper* font; if(!(font = m_fc.Create(m_hDC, lf))) { _tcscpy_s(lf.lfFaceName, _T("Arial")); if(!(font = m_fc.Create(m_hDC, lf))) { ASSERT(0); continue; } } HFONT hOldFont = SelectFont(m_hDC, *font); const TEXTMETRIC& tm = font->GetTextMetric(); for(LPCWSTR c = t.str; *c; c++) { CAutoPtr g(new Glyph()); g->c = *c; g->style = t.style; g->scale = scale; g->vertical = vertical; g->font = font; c_next = !c[1] && pos ? c_next = s->m_text.GetAt(pos).str[0] : c[1]; Arabic::Replace(g->c, c_prev, c_next); c_prev = c[0]; CSize extent; GetTextExtentPoint32W(m_hDC, &g->c, 1, &extent); ASSERT(extent.cx >= 0 && extent.cy >= 0); if(vertical) { g->spacing = (int)(t.style.font.spacing * scale.cy + 0.5); g->ascent = extent.cx / 2; g->descent = extent.cx - g->ascent; g->width = extent.cy; // TESTME if(g->c == Text::SP) { g->width /= 2; } } else { g->spacing = (int)(t.style.font.spacing * scale.cx + 0.5); g->ascent = tm.tmAscent; g->descent = tm.tmDescent; g->width = extent.cx; } if(g->c == Text::LSEP) { g->spacing = 0; g->width = 0; g->ascent /= 2; g->descent /= 2; } else { GlyphPath* path = m_gpc.Create(m_hDC, font, g->c); if(!path) {ASSERT(0); continue;} g->path = *path; } glyphs.AddTail(g); } SelectFont(m_hDC, hOldFont); } // break glyphs into rows CAutoPtrList rows; CAutoPtr row; pos = glyphs.GetHeadPosition(); while(pos) { CAutoPtr g = glyphs.GetNext(pos); if(!row) row.Attach(new Row()); WCHAR c = g->c; row->AddTail(g); if(c == Text::LSEP || !pos) rows.AddTail(row); } // kerning if(s->m_direction.primary == _T("right")) // || s->m_direction.primary == _T("left") { for(POSITION rpos = rows.GetHeadPosition(); rpos; rows.GetNext(rpos)) { Row* r = rows.GetAt(rpos); POSITION gpos = r->GetHeadPosition(); while(gpos) { Glyph* g1 = r->GetNext(gpos); if(!gpos) break; Glyph* g2 = r->GetAt(gpos); if(g1->font != g2->font || !g1->style.font.kerning || !g2->style.font.kerning) continue; if(int size = g1->font->GetKernAmount(g1->c, g2->c)) { g2->path.MovePoints(CPoint(size, 0)); g2->width += size; } } } } // wrap rows if(s->m_wrap == _T("normal") || s->m_wrap == _T("even")) { int maxwidth = abs((int)(vertical ? frame.Height() : frame.Width())); int minwidth = 0; for(POSITION rpos = rows.GetHeadPosition(); rpos; rows.GetNext(rpos)) { Row* r = rows.GetAt(rpos); POSITION brpos = NULL; if(s->m_wrap == _T("even")) { int fullwidth = 0; for(POSITION gpos = r->GetHeadPosition(); gpos; r->GetNext(gpos)) { const Glyph* g = r->GetAt(gpos); fullwidth += g->width + g->spacing; } fullwidth = abs(fullwidth); if(fullwidth > maxwidth) { maxwidth = fullwidth / ((fullwidth / maxwidth) + 1); minwidth = maxwidth; } } int width = 0; for(POSITION gpos = r->GetHeadPosition(); gpos; r->GetNext(gpos)) { const Glyph* g = r->GetAt(gpos); width += g->width + g->spacing; if(brpos && abs(width) > maxwidth && g->c != Text::SP) { row.Attach(new Row()); POSITION next = brpos; r->GetNext(next); do {row->AddHead(r->GetPrev(brpos));} while(brpos); rows.InsertBefore(rpos, row); while(!r->IsEmpty() && r->GetHeadPosition() != next) r->RemoveHeadNoReturn(); g = r->GetAt(gpos = next); width = g->width + g->spacing; } if(abs(width) >= minwidth) { if(g->style.linebreak == _T("char") || g->style.linebreak == _T("word") && g->c == Text::SP) { brpos = gpos; } } } } } // trim rows for(POSITION pos = rows.GetHeadPosition(); pos; rows.GetNext(pos)) { Row* r = rows.GetAt(pos); while(!r->IsEmpty() && r->GetHead()->c == Text::SP) r->RemoveHead(); while(!r->IsEmpty() && r->GetTail()->c == Text::SP) r->RemoveTail(); } // calc fill width for each glyph CAtlList glypsh2fill; int fill_id = 0; int fill_width = 0; for(POSITION pos = rows.GetHeadPosition(); pos; rows.GetNext(pos)) { Row* r = rows.GetAt(pos); POSITION gpos = r->GetHeadPosition(); while(gpos) { Glyph* g = r->GetNext(gpos); if(!glypsh2fill.IsEmpty() && fill_id && (g->style.fill.id != fill_id || !pos && !gpos)) { int w = (int)(g->style.fill.width * fill_width + 0.5); while(!glypsh2fill.IsEmpty()) { Glyph* g = glypsh2fill.RemoveTail(); fill_width -= g->width; g->fill = w - fill_width; } ASSERT(glypsh2fill.IsEmpty()); ASSERT(fill_width == 0); glypsh2fill.RemoveAll(); fill_width = 0; } fill_id = g->style.fill.id; if(g->style.fill.id) { glypsh2fill.AddTail(g); fill_width += g->width; } } } // calc row sizes and total subtitle size CSize size(0, 0); if(s->m_direction.secondary == _T("left") || s->m_direction.secondary == _T("up")) ReverseList(rows); for(POSITION pos = rows.GetHeadPosition(); pos; rows.GetNext(pos)) { Row* r = rows.GetAt(pos); if(s->m_direction.primary == _T("left") || s->m_direction.primary == _T("up")) ReverseList(*r); int w = 0, h = 0; r->width = 0; for(POSITION gpos = r->GetHeadPosition(); gpos; r->GetNext(gpos)) { const Glyph* g = r->GetAt(gpos); w += g->width; if(gpos) w += g->spacing; h = max(h, g->ascent + g->descent); r->width += g->width; if(gpos) r->width += g->spacing; r->ascent = max(r->ascent, g->ascent); r->descent = max(r->descent, g->descent); r->border = max(r->border, g->GetBackgroundSize()); } for(POSITION gpos = r->GetHeadPosition(); gpos; r->GetNext(gpos)) { Glyph* g = r->GetAt(gpos); g->row_ascent = r->ascent; g->row_descent = r->descent; } if(vertical) { size.cx += h; size.cy = max(size.cy, w); } else { size.cx = max(size.cx, w); size.cy += h; } } // align rows and calc glyph positions rs = new RenderedSubtitle(spdrc, clip); CPoint p = GetAlignPoint(style.placement, scale, frame, size); CPoint org = GetAlignPoint(style.placement, scale, frame); // collision detection if(!s->m_animated) { int tlb = !rows.IsEmpty() ? rows.GetHead()->border : 0; int brb = !rows.IsEmpty() ? rows.GetTail()->border : 0; CRect r(p, size); m_sra.GetRect(r, s, style.placement.align, tlb, brb); org += r.TopLeft() - p; p = r.TopLeft(); } CRect subrect(p, size); // continue positioning for(POSITION pos = rows.GetHeadPosition(); pos; rows.GetNext(pos)) { Row* r = rows.GetAt(pos); CSize rsize; rsize.cx = rsize.cy = r->width; if(vertical) { p.y = GetAlignPoint(style.placement, scale, frame, rsize).y; for(POSITION gpos = r->GetHeadPosition(); gpos; r->GetNext(gpos)) { CAutoPtr g = r->GetAt(gpos); g->tl.x = p.x + (int)(g->style.placement.offset.x * scale.cx + 0.5) + r->ascent - g->ascent; g->tl.y = p.y + (int)(g->style.placement.offset.y * scale.cy + 0.5); p.y += g->width + g->spacing; rs->m_glyphs.AddTail(g); } p.x += r->ascent + r->descent; } else { p.x = GetAlignPoint(style.placement, scale, frame, rsize).x; for(POSITION gpos = r->GetHeadPosition(); gpos; r->GetNext(gpos)) { CAutoPtr g = r->GetAt(gpos); g->tl.x = p.x + (int)(g->style.placement.offset.x * scale.cx + 0.5); g->tl.y = p.y + (int)(g->style.placement.offset.y * scale.cy + 0.5) + r->ascent - g->ascent; p.x += g->width + g->spacing; rs->m_glyphs.AddTail(g); } p.y += r->ascent + r->descent; } } // bkg, precalc style.placement.path, transform pos = rs->m_glyphs.GetHeadPosition(); while(pos) { Glyph* g = rs->m_glyphs.GetNext(pos); g->CreateBkg(); g->CreateSplineCoeffs(spdrc); g->Transform(org, subrect); } // merge glyphs (TODO: merge 'fill' too) Glyph* g0 = NULL; pos = rs->m_glyphs.GetHeadPosition(); while(pos) { POSITION cur = pos; Glyph* g = rs->m_glyphs.GetNext(pos); CRect r = g->bbox + g->tl; int size = (int)(g->GetBackgroundSize() + 0.5); int depth = (int)(g->GetShadowDepth() + 0.5); r.InflateRect(size, size); r.InflateRect(depth, depth); r.left >>= 6; r.top >>= 6; r.right = (r.right + 32) >> 6; r.bottom = (r.bottom + 32) >> 6; if((r & clip).IsRectEmpty()) // clip { rs->m_glyphs.RemoveAt(cur); } else if(g0 && g0->style.IsSimilar(g->style)) // append { CPoint o = g->tl - g0->tl; g->path.MovePoints(o); g0->path.types.Append(g->path.types); g0->path.points.Append(g->path.points); g->path_bkg.MovePoints(o); g0->path_bkg.types.Append(g->path_bkg.types); g0->path_bkg.points.Append(g->path_bkg.points); g0->bbox |= g->bbox + o; rs->m_glyphs.RemoveAt(cur); } else // leave alone { g0 = g; } } // rasterize pos = rs->m_glyphs.GetHeadPosition(); while(pos) rs->m_glyphs.GetNext(pos)->Rasterize(); // cache m_rsc.Add(s->m_name, rs); m_fc.Flush(); return rs; } // CRect RenderedSubtitle::Draw(SubPicDesc& spd) const { CRect bbox; bbox.SetRectEmpty(); // shadow POSITION pos = m_glyphs.GetHeadPosition(); while(pos) { Glyph* g = m_glyphs.GetNext(pos); if(g->style.shadow.depth <= 0) continue; DWORD c = g->style.shadow.color; DWORD sw[6] = {c, -1}; bool outline = g->style.background.type == L"outline" && g->style.background.size > 0; bbox |= g->ras_shadow.Draw(spd, m_clip, g->tls.x, g->tls.y, sw, outline ? 1 : 0); } // background pos = m_glyphs.GetHeadPosition(); while(pos) { Glyph* g = m_glyphs.GetNext(pos); DWORD c = g->style.background.color; DWORD sw[6] = {c, -1}; if(g->style.background.type == L"outline" && g->style.background.size > 0) { bbox |= g->ras.Draw(spd, m_clip, g->tl.x, g->tl.y, sw, g->style.font.color.a < 255 ? 2 : 1); } else if(g->style.background.type == L"enlarge" && g->style.background.size > 0 || g->style.background.type == L"box" && g->style.background.size >= 0) { bbox |= g->ras_bkg.Draw(spd, m_clip, g->tl.x, g->tl.y, sw, 0); } } // body pos = m_glyphs.GetHeadPosition(); while(pos) { Glyph* g = m_glyphs.GetNext(pos); DWORD c = g->style.font.color; DWORD sw[6] = {c, -1}; // TODO: fill bbox |= g->ras.Draw(spd, m_clip, g->tl.x, g->tl.y, sw, 0); } return bbox; } // void SubRectAllocator::UpdateTarget(const CSize& s, const CRect& r) { if(vs != s || vr != r) RemoveAll(); vs = s; vr = r; } void SubRectAllocator::GetRect(CRect& rect, const Subtitle* s, const Align& align, int tlb, int brb) { SubRect sr(rect, s->m_layer); sr.rect.InflateRect(tlb, tlb, brb, brb); StringMapW::CPair* pPair = Lookup(s->m_name); if(pPair && pPair->m_value.rect != sr.rect) { RemoveKey(s->m_name); pPair = NULL; } if(!pPair) { bool vertical = s->m_direction.primary == _T("down") || s->m_direction.primary == _T("up"); bool fOK = false; while(!fOK) { fOK = true; POSITION pos = GetStartPosition(); while(pos) { const SubRect& sr2 = GetNextValue(pos); if(sr.layer == sr2.layer && !(sr.rect & sr2.rect).IsRectEmpty()) { if(vertical) { if(align.h < 0.5) { sr.rect.right = sr2.rect.right + sr.rect.Width(); sr.rect.left = sr2.rect.right; } else { sr.rect.left = sr2.rect.left - sr.rect.Width(); sr.rect.right = sr2.rect.left; } } else { if(align.v < 0.5) { sr.rect.bottom = sr2.rect.bottom + sr.rect.Height(); sr.rect.top = sr2.rect.bottom; } else { sr.rect.top = sr2.rect.top - sr.rect.Height(); sr.rect.bottom = sr2.rect.top; } } fOK = false; } } } SetAt(s->m_name, sr); rect = sr.rect; rect.DeflateRect(tlb, tlb, brb, brb); } } // FontWrapper* FontCache::Create(HDC hDC, const LOGFONT& lf) { CStringW key; key.Format(L"%s,%d,%d,%d", CStringW(lf.lfFaceName), lf.lfHeight, lf.lfWeight, ((lf.lfItalic&1)<<2) | ((lf.lfUnderline&1)<<1) | ((lf.lfStrikeOut&1)<<0)); FontWrapper* pFW = NULL; if(m_key2obj.Lookup(key, pFW)) { return pFW; } HFONT hFont; if(!(hFont = CreateFontIndirect(&lf))) { ASSERT(0); return NULL; } pFW = new FontWrapper(hDC, hFont, key); Add(key, pFW, false); return pFW; } // GlyphPath* GlyphPathCache::Create(HDC hDC, const FontWrapper* f, WCHAR c) { CStringW key = CStringW((LPCWSTR)*f) + c; GlyphPath* path = NULL; if(m_key2obj.Lookup(key, path)) { return path; } BeginPath(hDC); TextOutW(hDC, 0, 0, &c, 1); CloseFigure(hDC); if(!EndPath(hDC)) {AbortPath(hDC); ASSERT(0); return NULL;} path = new GlyphPath(); int count = GetPath(hDC, NULL, NULL, 0); if(count > 0) { path->points.SetCount(count); path->types.SetCount(count); if(count != GetPath(hDC, path->points.GetData(), path->types.GetData(), count)) { ASSERT(0); delete path; return NULL; } } Add(key, path); return path; } }