Merge with MPC-HC 6d1472b2f18266d92e5bc068667de348c0cd3b3b.
[xy_vsfilter.git] / src / subtitles / libssf / Renderer.cpp
blobb39a83961da776e079991fd1b4ff640fa27a5cf0
1 /*
2 * Copyright (C) 2003-2006 Gabest
3 * http://www.gabest.org
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
8 * any later version.
9 *
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with GNU Make; see the file COPYING. If not, write to
17 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
18 * http://www.gnu.org/copyleft/gpl.html
20 * TODO: do something about bidi (at least handle these ranges: 0590-07BF, FB1D-FDFF, FE70-FEFF)
24 #include "stdafx.h"
25 #include "Renderer.h"
26 #include "Arabic.h"
28 namespace ssf
30 template <class T>
31 void ReverseList(T& l)
33 POSITION pos = l.GetHeadPosition();
34 while(pos)
36 POSITION cur = pos;
37 l.GetNext(pos);
38 l.AddHead(l.GetAt(cur));
39 l.RemoveAt(cur);
43 static CPoint GetAlignPoint(const Placement& placement, const Size& scale, const CRect& frame, const CSize& size)
45 CPoint p;
47 p.x = frame.left;
48 p.x += placement.pos.auto_x
49 ? placement.align.h * (frame.Width() - size.cx)
50 : placement.pos.x * scale.cx - placement.align.h * size.cx;
52 p.y = frame.top;
53 p.y += placement.pos.auto_y
54 ? placement.align.v * (frame.Height() - size.cy)
55 : placement.pos.y * scale.cy - placement.align.v * size.cy;
57 return p;
60 static CPoint GetAlignPoint(const Placement& placement, const Size& scale, const CRect& frame)
62 CSize size(0, 0);
63 return GetAlignPoint(placement, scale, frame, size);
68 Renderer::Renderer()
70 m_hDC = CreateCompatibleDC(NULL);
71 SetBkMode(m_hDC, TRANSPARENT);
72 SetTextColor(m_hDC, 0xffffff);
73 SetMapMode(m_hDC, MM_TEXT);
76 Renderer::~Renderer()
78 DeleteDC(m_hDC);
81 void Renderer::NextSegment(const CAutoPtrList<Subtitle>& subs)
83 StringMapW<bool> names;
84 POSITION pos = subs.GetHeadPosition();
85 while(pos) names[subs.GetNext(pos)->m_name] = true;
87 pos = m_sra.GetStartPosition();
88 while(pos)
90 POSITION cur = pos;
91 const CStringW& name = m_sra.GetNextKey(pos);
92 if(!names.Lookup(name)) m_sra.RemoveAtPos(cur);
96 RenderedSubtitle* Renderer::Lookup(const Subtitle* s, const CSize& vs, const CRect& vr)
98 m_sra.UpdateTarget(vs, vr);
100 if(s->m_text.IsEmpty())
101 return NULL;
103 CRect spdrc = s->m_frame.reference == _T("video") ? vr : CRect(CPoint(0, 0), vs);
105 if(spdrc.IsRectEmpty())
106 return NULL;
108 RenderedSubtitle* rs = NULL;
110 if(m_rsc.Lookup(s->m_name, rs))
112 if(!s->m_animated && rs->m_spdrc == spdrc)
113 return rs;
115 m_rsc.Invalidate(s->m_name);
118 const Style& style = s->m_text.GetHead().style;
120 Size scale;
122 scale.cx = (float)spdrc.Width() / s->m_frame.resolution.cx;
123 scale.cy = (float)spdrc.Height() / s->m_frame.resolution.cy;
125 CRect frame;
127 frame.left = (int)(64.0f * (spdrc.left + style.placement.margin.l * scale.cx) + 0.5);
128 frame.top = (int)(64.0f * (spdrc.top + style.placement.margin.t * scale.cy) + 0.5);
129 frame.right = (int)(64.0f * (spdrc.right - style.placement.margin.r * scale.cx) + 0.5);
130 frame.bottom = (int)(64.0f * (spdrc.bottom - style.placement.margin.b * scale.cy) + 0.5);
132 CRect clip;
134 if(style.placement.clip.l == -1) clip.left = 0;
135 else clip.left = (int)(spdrc.left + style.placement.clip.l * scale.cx);
136 if(style.placement.clip.t == -1) clip.top = 0;
137 else clip.top = (int)(spdrc.top + style.placement.clip.t * scale.cy);
138 if(style.placement.clip.r == -1) clip.right = vs.cx;
139 else clip.right = (int)(spdrc.left + style.placement.clip.r * scale.cx);
140 if(style.placement.clip.b == -1) clip.bottom = vs.cy;
141 else clip.bottom = (int)(spdrc.top + style.placement.clip.b * scale.cy);
143 clip.left = max(clip.left, 0);
144 clip.top = max(clip.top, 0);
145 clip.right = min(clip.right, vs.cx);
146 clip.bottom = min(clip.bottom, vs.cy);
148 scale.cx *= 64;
149 scale.cy *= 64;
151 bool vertical = s->m_direction.primary == _T("down") || s->m_direction.primary == _T("up");
153 // create glyph paths
155 WCHAR c_prev = 0, c_next;
157 CAutoPtrList<Glyph> glyphs;
159 POSITION pos = s->m_text.GetHeadPosition();
160 while(pos)
162 const Text& t = s->m_text.GetNext(pos);
164 LOGFONT lf;
165 memset(&lf, 0, sizeof(lf));
166 lf.lfCharSet = DEFAULT_CHARSET;
167 _tcscpy_s(lf.lfFaceName, CString(t.style.font.face));
168 lf.lfHeight = (LONG)(t.style.font.size * scale.cy + 0.5);
169 lf.lfWeight = (LONG)(t.style.font.weight + 0.5);
170 lf.lfItalic = !!t.style.font.italic;
171 lf.lfUnderline = !!t.style.font.underline;
172 lf.lfStrikeOut = !!t.style.font.strikethrough;
173 lf.lfOutPrecision = OUT_TT_PRECIS;
174 lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
175 lf.lfQuality = ANTIALIASED_QUALITY;
176 lf.lfPitchAndFamily = DEFAULT_PITCH|FF_DONTCARE;
178 FontWrapper* font;
180 if(!(font = m_fc.Create(m_hDC, lf)))
182 _tcscpy_s(lf.lfFaceName, _T("Arial"));
184 if(!(font = m_fc.Create(m_hDC, lf)))
186 ASSERT(0);
187 continue;
191 HFONT hOldFont = SelectFont(m_hDC, *font);
193 const TEXTMETRIC& tm = font->GetTextMetric();
195 for(LPCWSTR c = t.str; *c; c++)
197 CAutoPtr<Glyph> g(new Glyph());
199 g->c = *c;
200 g->style = t.style;
201 g->scale = scale;
202 g->vertical = vertical;
203 g->font = font;
205 c_next = !c[1] && pos ? c_next = s->m_text.GetAt(pos).str[0] : c[1];
206 Arabic::Replace(g->c, c_prev, c_next);
207 c_prev = c[0];
209 CSize extent;
210 GetTextExtentPoint32W(m_hDC, &g->c, 1, &extent);
211 ASSERT(extent.cx >= 0 && extent.cy >= 0);
213 if(vertical)
215 g->spacing = (int)(t.style.font.spacing * scale.cy + 0.5);
216 g->ascent = extent.cx / 2;
217 g->descent = extent.cx - g->ascent;
218 g->width = extent.cy;
220 // TESTME
221 if(g->c == Text::SP)
223 g->width /= 2;
226 else
228 g->spacing = (int)(t.style.font.spacing * scale.cx + 0.5);
229 g->ascent = tm.tmAscent;
230 g->descent = tm.tmDescent;
231 g->width = extent.cx;
234 if(g->c == Text::LSEP)
236 g->spacing = 0;
237 g->width = 0;
238 g->ascent /= 2;
239 g->descent /= 2;
241 else
243 GlyphPath* path = m_gpc.Create(m_hDC, font, g->c);
244 if(!path) {ASSERT(0); continue;}
245 g->path = *path;
248 glyphs.AddTail(g);
251 SelectFont(m_hDC, hOldFont);
254 // break glyphs into rows
256 CAutoPtrList<Row> rows;
257 CAutoPtr<Row> row;
259 pos = glyphs.GetHeadPosition();
260 while(pos)
262 CAutoPtr<Glyph> g = glyphs.GetNext(pos);
263 if(!row) row.Attach(new Row());
264 WCHAR c = g->c;
265 row->AddTail(g);
266 if(c == Text::LSEP || !pos) rows.AddTail(row);
269 // kerning
271 if(s->m_direction.primary == _T("right")) // || s->m_direction.primary == _T("left")
273 for(POSITION rpos = rows.GetHeadPosition(); rpos; rows.GetNext(rpos))
275 Row* r = rows.GetAt(rpos);
277 POSITION gpos = r->GetHeadPosition();
278 while(gpos)
280 Glyph* g1 = r->GetNext(gpos);
281 if(!gpos) break;
283 Glyph* g2 = r->GetAt(gpos);
284 if(g1->font != g2->font || !g1->style.font.kerning || !g2->style.font.kerning)
285 continue;
287 if(int size = g1->font->GetKernAmount(g1->c, g2->c))
289 g2->path.MovePoints(CPoint(size, 0));
290 g2->width += size;
296 // wrap rows
298 if(s->m_wrap == _T("normal") || s->m_wrap == _T("even"))
300 int maxwidth = abs((int)(vertical ? frame.Height() : frame.Width()));
301 int minwidth = 0;
303 for(POSITION rpos = rows.GetHeadPosition(); rpos; rows.GetNext(rpos))
305 Row* r = rows.GetAt(rpos);
307 POSITION brpos = NULL;
309 if(s->m_wrap == _T("even"))
311 int fullwidth = 0;
313 for(POSITION gpos = r->GetHeadPosition(); gpos; r->GetNext(gpos))
315 const Glyph* g = r->GetAt(gpos);
317 fullwidth += g->width + g->spacing;
320 fullwidth = abs(fullwidth);
322 if(fullwidth > maxwidth)
324 maxwidth = fullwidth / ((fullwidth / maxwidth) + 1);
325 minwidth = maxwidth;
329 int width = 0;
331 for(POSITION gpos = r->GetHeadPosition(); gpos; r->GetNext(gpos))
333 const Glyph* g = r->GetAt(gpos);
335 width += g->width + g->spacing;
337 if(brpos && abs(width) > maxwidth && g->c != Text::SP)
339 row.Attach(new Row());
340 POSITION next = brpos;
341 r->GetNext(next);
342 do {row->AddHead(r->GetPrev(brpos));} while(brpos);
343 rows.InsertBefore(rpos, row);
344 while(!r->IsEmpty() && r->GetHeadPosition() != next) r->RemoveHeadNoReturn();
345 g = r->GetAt(gpos = next);
346 width = g->width + g->spacing;
349 if(abs(width) >= minwidth)
351 if(g->style.linebreak == _T("char")
352 || g->style.linebreak == _T("word") && g->c == Text::SP)
354 brpos = gpos;
361 // trim rows
363 for(POSITION pos = rows.GetHeadPosition(); pos; rows.GetNext(pos))
365 Row* r = rows.GetAt(pos);
367 while(!r->IsEmpty() && r->GetHead()->c == Text::SP)
368 r->RemoveHead();
370 while(!r->IsEmpty() && r->GetTail()->c == Text::SP)
371 r->RemoveTail();
374 // calc fill width for each glyph
376 CAtlList<Glyph*> glypsh2fill;
377 int fill_id = 0;
378 int fill_width = 0;
380 for(POSITION pos = rows.GetHeadPosition(); pos; rows.GetNext(pos))
382 Row* r = rows.GetAt(pos);
384 POSITION gpos = r->GetHeadPosition();
385 while(gpos)
387 Glyph* g = r->GetNext(gpos);
389 if(!glypsh2fill.IsEmpty() && fill_id && (g->style.fill.id != fill_id || !pos && !gpos))
391 int w = (int)(g->style.fill.width * fill_width + 0.5);
393 while(!glypsh2fill.IsEmpty())
395 Glyph* g = glypsh2fill.RemoveTail();
396 fill_width -= g->width;
397 g->fill = w - fill_width;
400 ASSERT(glypsh2fill.IsEmpty());
401 ASSERT(fill_width == 0);
403 glypsh2fill.RemoveAll();
404 fill_width = 0;
407 fill_id = g->style.fill.id;
409 if(g->style.fill.id)
411 glypsh2fill.AddTail(g);
412 fill_width += g->width;
417 // calc row sizes and total subtitle size
419 CSize size(0, 0);
421 if(s->m_direction.secondary == _T("left") || s->m_direction.secondary == _T("up"))
422 ReverseList(rows);
424 for(POSITION pos = rows.GetHeadPosition(); pos; rows.GetNext(pos))
426 Row* r = rows.GetAt(pos);
428 if(s->m_direction.primary == _T("left") || s->m_direction.primary == _T("up"))
429 ReverseList(*r);
431 int w = 0, h = 0;
433 r->width = 0;
435 for(POSITION gpos = r->GetHeadPosition(); gpos; r->GetNext(gpos))
437 const Glyph* g = r->GetAt(gpos);
439 w += g->width;
440 if(gpos) w += g->spacing;
441 h = max(h, g->ascent + g->descent);
443 r->width += g->width;
444 if(gpos) r->width += g->spacing;
445 r->ascent = max(r->ascent, g->ascent);
446 r->descent = max(r->descent, g->descent);
447 r->border = max(r->border, g->GetBackgroundSize());
450 for(POSITION gpos = r->GetHeadPosition(); gpos; r->GetNext(gpos))
452 Glyph* g = r->GetAt(gpos);
453 g->row_ascent = r->ascent;
454 g->row_descent = r->descent;
457 if(vertical)
459 size.cx += h;
460 size.cy = max(size.cy, w);
462 else
464 size.cx = max(size.cx, w);
465 size.cy += h;
469 // align rows and calc glyph positions
471 rs = new RenderedSubtitle(spdrc, clip);
473 CPoint p = GetAlignPoint(style.placement, scale, frame, size);
474 CPoint org = GetAlignPoint(style.placement, scale, frame);
476 // collision detection
478 if(!s->m_animated)
480 int tlb = !rows.IsEmpty() ? rows.GetHead()->border : 0;
481 int brb = !rows.IsEmpty() ? rows.GetTail()->border : 0;
483 CRect r(p, size);
484 m_sra.GetRect(r, s, style.placement.align, tlb, brb);
485 org += r.TopLeft() - p;
486 p = r.TopLeft();
489 CRect subrect(p, size);
491 // continue positioning
493 for(POSITION pos = rows.GetHeadPosition(); pos; rows.GetNext(pos))
495 Row* r = rows.GetAt(pos);
497 CSize rsize;
498 rsize.cx = rsize.cy = r->width;
500 if(vertical)
502 p.y = GetAlignPoint(style.placement, scale, frame, rsize).y;
504 for(POSITION gpos = r->GetHeadPosition(); gpos; r->GetNext(gpos))
506 CAutoPtr<Glyph> g = r->GetAt(gpos);
507 g->tl.x = p.x + (int)(g->style.placement.offset.x * scale.cx + 0.5) + r->ascent - g->ascent;
508 g->tl.y = p.y + (int)(g->style.placement.offset.y * scale.cy + 0.5);
509 p.y += g->width + g->spacing;
510 rs->m_glyphs.AddTail(g);
513 p.x += r->ascent + r->descent;
515 else
517 p.x = GetAlignPoint(style.placement, scale, frame, rsize).x;
519 for(POSITION gpos = r->GetHeadPosition(); gpos; r->GetNext(gpos))
521 CAutoPtr<Glyph> g = r->GetAt(gpos);
522 g->tl.x = p.x + (int)(g->style.placement.offset.x * scale.cx + 0.5);
523 g->tl.y = p.y + (int)(g->style.placement.offset.y * scale.cy + 0.5) + r->ascent - g->ascent;
524 p.x += g->width + g->spacing;
525 rs->m_glyphs.AddTail(g);
528 p.y += r->ascent + r->descent;
532 // bkg, precalc style.placement.path, transform
534 pos = rs->m_glyphs.GetHeadPosition();
535 while(pos)
537 Glyph* g = rs->m_glyphs.GetNext(pos);
538 g->CreateBkg();
539 g->CreateSplineCoeffs(spdrc);
540 g->Transform(org, subrect);
543 // merge glyphs (TODO: merge 'fill' too)
545 Glyph* g0 = NULL;
547 pos = rs->m_glyphs.GetHeadPosition();
548 while(pos)
550 POSITION cur = pos;
552 Glyph* g = rs->m_glyphs.GetNext(pos);
554 CRect r = g->bbox + g->tl;
556 int size = (int)(g->GetBackgroundSize() + 0.5);
557 int depth = (int)(g->GetShadowDepth() + 0.5);
559 r.InflateRect(size, size);
560 r.InflateRect(depth, depth);
562 r.left >>= 6;
563 r.top >>= 6;
564 r.right = (r.right + 32) >> 6;
565 r.bottom = (r.bottom + 32) >> 6;
567 if((r & clip).IsRectEmpty()) // clip
569 rs->m_glyphs.RemoveAt(cur);
571 else if(g0 && g0->style.IsSimilar(g->style)) // append
573 CPoint o = g->tl - g0->tl;
575 g->path.MovePoints(o);
577 g0->path.types.Append(g->path.types);
578 g0->path.points.Append(g->path.points);
580 g->path_bkg.MovePoints(o);
582 g0->path_bkg.types.Append(g->path_bkg.types);
583 g0->path_bkg.points.Append(g->path_bkg.points);
585 g0->bbox |= g->bbox + o;
587 rs->m_glyphs.RemoveAt(cur);
589 else // leave alone
591 g0 = g;
595 // rasterize
597 pos = rs->m_glyphs.GetHeadPosition();
598 while(pos) rs->m_glyphs.GetNext(pos)->Rasterize();
600 // cache
602 m_rsc.Add(s->m_name, rs);
604 m_fc.Flush();
606 return rs;
611 CRect RenderedSubtitle::Draw(SubPicDesc& spd) const
613 CRect bbox;
614 bbox.SetRectEmpty();
616 // shadow
618 POSITION pos = m_glyphs.GetHeadPosition();
619 while(pos)
621 Glyph* g = m_glyphs.GetNext(pos);
623 if(g->style.shadow.depth <= 0) continue;
625 DWORD c = g->style.shadow.color;
626 DWORD sw[6] = {c, -1};
628 bool outline = g->style.background.type == L"outline" && g->style.background.size > 0;
630 bbox |= g->ras_shadow.Draw(spd, m_clip, g->tls.x, g->tls.y, sw, outline ? 1 : 0);
633 // background
635 pos = m_glyphs.GetHeadPosition();
636 while(pos)
638 Glyph* g = m_glyphs.GetNext(pos);
640 DWORD c = g->style.background.color;
641 DWORD sw[6] = {c, -1};
643 if(g->style.background.type == L"outline" && g->style.background.size > 0)
645 bbox |= g->ras.Draw(spd, m_clip, g->tl.x, g->tl.y, sw, g->style.font.color.a < 255 ? 2 : 1);
647 else if(g->style.background.type == L"enlarge" && g->style.background.size > 0
648 || g->style.background.type == L"box" && g->style.background.size >= 0)
650 bbox |= g->ras_bkg.Draw(spd, m_clip, g->tl.x, g->tl.y, sw, 0);
654 // body
656 pos = m_glyphs.GetHeadPosition();
657 while(pos)
659 Glyph* g = m_glyphs.GetNext(pos);
661 DWORD c = g->style.font.color;
662 DWORD sw[6] = {c, -1}; // TODO: fill
664 bbox |= g->ras.Draw(spd, m_clip, g->tl.x, g->tl.y, sw, 0);
667 return bbox;
672 void SubRectAllocator::UpdateTarget(const CSize& s, const CRect& r)
674 if(vs != s || vr != r) RemoveAll();
675 vs = s;
676 vr = r;
679 void SubRectAllocator::GetRect(CRect& rect, const Subtitle* s, const Align& align, int tlb, int brb)
681 SubRect sr(rect, s->m_layer);
682 sr.rect.InflateRect(tlb, tlb, brb, brb);
684 StringMapW<SubRect>::CPair* pPair = Lookup(s->m_name);
686 if(pPair && pPair->m_value.rect != sr.rect)
688 RemoveKey(s->m_name);
689 pPair = NULL;
692 if(!pPair)
694 bool vertical = s->m_direction.primary == _T("down") || s->m_direction.primary == _T("up");
696 bool fOK = false;
698 while(!fOK)
700 fOK = true;
702 POSITION pos = GetStartPosition();
703 while(pos)
705 const SubRect& sr2 = GetNextValue(pos);
707 if(sr.layer == sr2.layer && !(sr.rect & sr2.rect).IsRectEmpty())
709 if(vertical)
711 if(align.h < 0.5)
713 sr.rect.right = sr2.rect.right + sr.rect.Width();
714 sr.rect.left = sr2.rect.right;
716 else
718 sr.rect.left = sr2.rect.left - sr.rect.Width();
719 sr.rect.right = sr2.rect.left;
722 else
724 if(align.v < 0.5)
726 sr.rect.bottom = sr2.rect.bottom + sr.rect.Height();
727 sr.rect.top = sr2.rect.bottom;
729 else
731 sr.rect.top = sr2.rect.top - sr.rect.Height();
732 sr.rect.bottom = sr2.rect.top;
736 fOK = false;
741 SetAt(s->m_name, sr);
743 rect = sr.rect;
744 rect.DeflateRect(tlb, tlb, brb, brb);
750 FontWrapper* FontCache::Create(HDC hDC, const LOGFONT& lf)
752 CStringW key;
754 key.Format(L"%s,%d,%d,%d",
755 CStringW(lf.lfFaceName), lf.lfHeight, lf.lfWeight,
756 ((lf.lfItalic&1)<<2) | ((lf.lfUnderline&1)<<1) | ((lf.lfStrikeOut&1)<<0));
758 FontWrapper* pFW = NULL;
760 if(m_key2obj.Lookup(key, pFW))
762 return pFW;
765 HFONT hFont;
767 if(!(hFont = CreateFontIndirect(&lf)))
769 ASSERT(0);
770 return NULL;
773 pFW = new FontWrapper(hDC, hFont, key);
775 Add(key, pFW, false);
777 return pFW;
782 GlyphPath* GlyphPathCache::Create(HDC hDC, const FontWrapper* f, WCHAR c)
784 CStringW key = CStringW((LPCWSTR)*f) + c;
786 GlyphPath* path = NULL;
788 if(m_key2obj.Lookup(key, path))
790 return path;
793 BeginPath(hDC);
794 TextOutW(hDC, 0, 0, &c, 1);
795 CloseFigure(hDC);
796 if(!EndPath(hDC)) {AbortPath(hDC); ASSERT(0); return NULL;}
798 path = new GlyphPath();
800 int count = GetPath(hDC, NULL, NULL, 0);
802 if(count > 0)
804 path->points.SetCount(count);
805 path->types.SetCount(count);
807 if(count != GetPath(hDC, path->points.GetData(), path->types.GetData(), count))
809 ASSERT(0);
810 delete path;
811 return NULL;
815 Add(key, path);
817 return path;