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)
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)
31 void ReverseList(T
& l
)
33 POSITION pos
= l
.GetHeadPosition();
38 l
.AddHead(l
.GetAt(cur
));
43 static CPoint
GetAlignPoint(const Placement
& placement
, const Size
& scale
, const CRect
& frame
, const CSize
& size
)
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
;
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
;
60 static CPoint
GetAlignPoint(const Placement
& placement
, const Size
& scale
, const CRect
& frame
)
63 return GetAlignPoint(placement
, scale
, frame
, size
);
70 m_hDC
= CreateCompatibleDC(NULL
);
71 SetBkMode(m_hDC
, TRANSPARENT
);
72 SetTextColor(m_hDC
, 0xffffff);
73 SetMapMode(m_hDC
, MM_TEXT
);
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();
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())
103 CRect spdrc
= s
->m_frame
.reference
== _T("video") ? vr
: CRect(CPoint(0, 0), vs
);
105 if(spdrc
.IsRectEmpty())
108 RenderedSubtitle
* rs
= NULL
;
110 if(m_rsc
.Lookup(s
->m_name
, rs
))
112 if(!s
->m_animated
&& rs
->m_spdrc
== spdrc
)
115 m_rsc
.Invalidate(s
->m_name
);
118 const Style
& style
= s
->m_text
.GetHead().style
;
122 scale
.cx
= (float)spdrc
.Width() / s
->m_frame
.resolution
.cx
;
123 scale
.cy
= (float)spdrc
.Height() / s
->m_frame
.resolution
.cy
;
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);
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
);
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();
162 const Text
& t
= s
->m_text
.GetNext(pos
);
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
;
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
)))
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());
202 g
->vertical
= vertical
;
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
);
210 GetTextExtentPoint32W(m_hDC
, &g
->c
, 1, &extent
);
211 ASSERT(extent
.cx
>= 0 && extent
.cy
>= 0);
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
;
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
)
243 GlyphPath
* path
= m_gpc
.Create(m_hDC
, font
, g
->c
);
244 if(!path
) {ASSERT(0); continue;}
251 SelectFont(m_hDC
, hOldFont
);
254 // break glyphs into rows
256 CAutoPtrList
<Row
> rows
;
259 pos
= glyphs
.GetHeadPosition();
262 CAutoPtr
<Glyph
> g
= glyphs
.GetNext(pos
);
263 if(!row
) row
.Attach(new Row());
266 if(c
== Text::LSEP
|| !pos
) rows
.AddTail(row
);
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();
280 Glyph
* g1
= r
->GetNext(gpos
);
283 Glyph
* g2
= r
->GetAt(gpos
);
284 if(g1
->font
!= g2
->font
|| !g1
->style
.font
.kerning
|| !g2
->style
.font
.kerning
)
287 if(int size
= g1
->font
->GetKernAmount(g1
->c
, g2
->c
))
289 g2
->path
.MovePoints(CPoint(size
, 0));
298 if(s
->m_wrap
== _T("normal") || s
->m_wrap
== _T("even"))
300 int maxwidth
= abs((int)(vertical
? frame
.Height() : frame
.Width()));
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"))
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);
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
;
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
)
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
)
370 while(!r
->IsEmpty() && r
->GetTail()->c
== Text::SP
)
374 // calc fill width for each glyph
376 CAtlList
<Glyph
*> glypsh2fill
;
380 for(POSITION pos
= rows
.GetHeadPosition(); pos
; rows
.GetNext(pos
))
382 Row
* r
= rows
.GetAt(pos
);
384 POSITION gpos
= r
->GetHeadPosition();
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();
407 fill_id
= g
->style
.fill
.id
;
411 glypsh2fill
.AddTail(g
);
412 fill_width
+= g
->width
;
417 // calc row sizes and total subtitle size
421 if(s
->m_direction
.secondary
== _T("left") || s
->m_direction
.secondary
== _T("up"))
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"))
435 for(POSITION gpos
= r
->GetHeadPosition(); gpos
; r
->GetNext(gpos
))
437 const Glyph
* g
= r
->GetAt(gpos
);
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
;
460 size
.cy
= max(size
.cy
, w
);
464 size
.cx
= max(size
.cx
, w
);
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
480 int tlb
= !rows
.IsEmpty() ? rows
.GetHead()->border
: 0;
481 int brb
= !rows
.IsEmpty() ? rows
.GetTail()->border
: 0;
484 m_sra
.GetRect(r
, s
, style
.placement
.align
, tlb
, brb
);
485 org
+= r
.TopLeft() - p
;
489 CRect
subrect(p
, size
);
491 // continue positioning
493 for(POSITION pos
= rows
.GetHeadPosition(); pos
; rows
.GetNext(pos
))
495 Row
* r
= rows
.GetAt(pos
);
498 rsize
.cx
= rsize
.cy
= r
->width
;
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
;
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();
537 Glyph
* g
= rs
->m_glyphs
.GetNext(pos
);
539 g
->CreateSplineCoeffs(spdrc
);
540 g
->Transform(org
, subrect
);
543 // merge glyphs (TODO: merge 'fill' too)
547 pos
= rs
->m_glyphs
.GetHeadPosition();
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
);
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
);
597 pos
= rs
->m_glyphs
.GetHeadPosition();
598 while(pos
) rs
->m_glyphs
.GetNext(pos
)->Rasterize();
602 m_rsc
.Add(s
->m_name
, rs
);
611 CRect
RenderedSubtitle::Draw(SubPicDesc
& spd
) const
618 POSITION pos
= m_glyphs
.GetHeadPosition();
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);
635 pos
= m_glyphs
.GetHeadPosition();
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);
656 pos
= m_glyphs
.GetHeadPosition();
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);
672 void SubRectAllocator::UpdateTarget(const CSize
& s
, const CRect
& r
)
674 if(vs
!= s
|| vr
!= r
) RemoveAll();
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
);
694 bool vertical
= s
->m_direction
.primary
== _T("down") || s
->m_direction
.primary
== _T("up");
702 POSITION pos
= GetStartPosition();
705 const SubRect
& sr2
= GetNextValue(pos
);
707 if(sr
.layer
== sr2
.layer
&& !(sr
.rect
& sr2
.rect
).IsRectEmpty())
713 sr
.rect
.right
= sr2
.rect
.right
+ sr
.rect
.Width();
714 sr
.rect
.left
= sr2
.rect
.right
;
718 sr
.rect
.left
= sr2
.rect
.left
- sr
.rect
.Width();
719 sr
.rect
.right
= sr2
.rect
.left
;
726 sr
.rect
.bottom
= sr2
.rect
.bottom
+ sr
.rect
.Height();
727 sr
.rect
.top
= sr2
.rect
.bottom
;
731 sr
.rect
.top
= sr2
.rect
.top
- sr
.rect
.Height();
732 sr
.rect
.bottom
= sr2
.rect
.top
;
741 SetAt(s
->m_name
, sr
);
744 rect
.DeflateRect(tlb
, tlb
, brb
, brb
);
750 FontWrapper
* FontCache::Create(HDC hDC
, const LOGFONT
& lf
)
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
))
767 if(!(hFont
= CreateFontIndirect(&lf
)))
773 pFW
= new FontWrapper(hDC
, hFont
, key
);
775 Add(key
, pFW
, false);
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
))
794 TextOutW(hDC
, 0, 0, &c
, 1);
796 if(!EndPath(hDC
)) {AbortPath(hDC
); ASSERT(0); return NULL
;}
798 path
= new GlyphPath();
800 int count
= GetPath(hDC
, NULL
, NULL
, 0);
804 path
->points
.SetCount(count
);
805 path
->types
.SetCount(count
);
807 if(count
!= GetPath(hDC
, path
->points
.GetData(), path
->types
.GetData(), count
))