STDMETHODIMP CRenderer::Render(SubPicDesc& spd, REFERENCE_TIME rt, double fps, RECT& bbox) { CheckPointer(m_file, E_UNEXPECTED); CheckPointer(m_renderer, E_UNEXPECTED); if(spd.type != MSP_RGB32) return E_INVALIDARG; CAutoLock csAutoLock(m_pLock); CRect bbox2; bbox2.SetRectEmpty(); CAutoPtrList<Subtitle> subs; m_file->Lookup((float)rt/10000000, subs); m_renderer->NextSegment(subs); POSITION pos = subs.GetHeadPosition(); while(pos) { const Subtitle* s = subs.GetNext(pos); const RenderedSubtitle* rs = m_renderer->Lookup(s, CSize(spd.w, spd.h), spd.vidrect); if(rs) bbox2 |= rs->Draw(spd); } bbox = bbox2 & CRect(0, 0, spd.w, spd.h); return S_OK; }
void Renderer::NextSegment(const CAutoPtrList<Subtitle>& subs) { StringMapW<bool> 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); } }
bool SubtitleFile::Lookup(float at, CAutoPtrList<Subtitle>& subs) { if(!subs.IsEmpty()) {ASSERT(0); return false;} CAtlList<SegmentItem> sis; m_segments.Lookup(at, sis); POSITION pos = sis.GetHeadPosition(); while(pos) { SegmentItem& si = sis.GetNext(pos); CAutoPtr<Subtitle> s(DNew Subtitle(this)); if(s->Parse(si.pDef, si.start, si.stop, at)) { for(POSITION pos = subs.GetHeadPosition(); pos; subs.GetNext(pos)) { if(s->m_layer < subs.GetAt(pos)->m_layer) { subs.InsertBefore(pos, s); break; } } if(s) { subs.AddTail(s); } } } return !subs.IsEmpty(); }
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<Glyph> 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 = m_fc.Create(m_hDC, lf); if(!font) { _tcscpy_s(lf.lfFaceName, _T("Arial")); font = m_fc.Create(m_hDC, lf); if(!font) { ASSERT(0); continue; } } HFONT hOldFont = SelectFont(m_hDC, *font); const TEXTMETRIC& tm = font->GetTextMetric(); for(LPCWSTR c = t.str; *c; c++) { CAutoPtr<Glyph> g(DNew 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<Row> rows; CAutoPtr<Row> row; pos = glyphs.GetHeadPosition(); while(pos) { CAutoPtr<Glyph> g = glyphs.GetNext(pos); if(!row) { row.Attach(DNew 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(DNew 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<Glyph*> 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 = DNew 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<Glyph> 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<Glyph> 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; }