1%% 2%% wpc_tt.erl -- 3%% 4%% Functions for reading TrueType fonts (.tt) 5%% 6%% Copyright (c) 2001-2011 Howard Trickey & Dan Gudmundsson 7%% 8%% See the file "license.terms" for information on usage and redistribution 9%% of this file, and for a DISCLAIMER OF ALL WARRANTIES. 10%% 11%% Rewritten to support Unicode codepoints and more complete support of 12%% the standard, and ttfc and otc files. /Dan 13%% 14 15%% For TrueType format, see http://www.microsoft.com/typography/otspec/ 16 17-module(wpc_tt). 18-export([init/0,menu/2,command/2, 19 init_font/2, sysfontdirs/0, process_ttfs/1, trygen/3, trygen/5, find_font_info/1 % debugging 20 ]). % for ai 21 22-import(lists, [reverse/1,sort/2,keysearch/3,duplicate/2,nthtail/2, 23 mapfoldl/3,foldl/3,sublist/3,map/2,last/1,seq/2,seq/3, 24 flatten/1,sum/1,append/1]). 25 26-include_lib("src/wings.hrl"). 27-include_lib("e3d/e3d.hrl"). 28 29-record(ttf_info, 30 {num_glyphs, %% Number of Glyphs 31 32 %% Offsets to table locations 33 loca, glyf, head, hhea, hmtx, kern, name, os2, 34 cff, %% undefined or initatied CFF info 35 index_map, %% A Cmap mapping for out chosen character encoding 36 index_to_loc_format, %% Format needed to map from glyph index to glyph 37 file, %% Filename 38 collection=0, %% Collection number 39 data %% The binary file 40 }). 41 42-record(polyarea, 43 {boundary, %%list of cedges (CCW oriented, closed) 44 islands=[]}). %%list of lists of cedges (CW, closed) 45 46-record(vertex, {pos, c, c1, type}). 47 48-type ttf() :: #ttf_info{}. 49%% -type scale() :: Uniform::float() | {ScaleX::float(),ScaleY::float()}. 50%% -type shift() :: Uniform::float() | {ShiftX::float(),ShiftY::float()}. 51%% -type size() :: {Width::integer(), Height::integer()}. 52-type vertex() :: #vertex{}. 53-type platform() :: unicode | mac | microsoft | integer(). 54-type encoding() :: unicode | roman | integer(). 55-type language() :: english | integer(). %% 0 if platform is unicode 56 57-define (fsITALIC, 2#00000001). 58-define (fsBOLD, 2#00100000). 59 60%% PLATFORM ID 61-define(PLATFORM_ID_UNICODE, 0). 62-define(PLATFORM_ID_MAC, 1). 63-define(PLATFORM_ID_ISO, 2). 64-define(PLATFORM_ID_MICROSOFT,3). 65 66%% encodingID for PLATFORM_ID_UNICODE 67-define(UNICODE_EID_UNICODE_1_0 ,0). 68-define(UNICODE_EID_UNICODE_1_1 ,1). 69-define(UNICODE_EID_ISO_10646 ,2). 70-define(UNICODE_EID_UNICODE_2_0_BMP,3). 71-define(UNICODE_EID_UNICODE_2_0_FULL,4). 72 73%% encodingID for PLATFORM_ID_MICROSOFT 74-define(MS_EID_SYMBOL ,0). 75-define(MS_EID_UNICODE_BMP ,1). 76-define(MS_EID_SHIFTJIS ,2). 77-define(MS_EID_UNICODE_FULL ,10). 78 79%% encodingID for PLATFORM_ID_MAC; same as Script Manager codes 80-define(MAC_EID_ROMAN ,0). 81-define(MAC_EID_JAPANESE ,1). 82-define(MAC_EID_CHINESE_TRAD ,2). 83-define(MAC_EID_KOREAN ,3). 84-define(MAC_EID_ARABIC ,4). 85-define(MAC_EID_HEBREW ,5). 86-define(MAC_EID_GREEK ,6). 87-define(MAC_EID_RUSSIAN ,7). 88 89-define(S16, 16/signed). 90-define(U16, 16/unsigned). 91-define(S32, 32/signed). 92-define(U32, 32/unsigned). 93-define(SKIP, _/binary). 94 95-define(DBG(F,A), ok). 96%-define(DBG(F,A), io:format("~w:~w: "++ F, [?MODULE,?LINE] ++ A)). 97 98init() -> true. 99 100menu({shape}, Menu) -> 101 insert_before(Menu); 102menu(_, Menu) -> Menu. 103 104insert_before([{_,grid,_,_}=Grid|Rest]) -> 105 [Grid,separator,menu_entry()|Rest]; 106insert_before([H|T]) -> 107 [H|insert_before(T)]; 108insert_before([]) -> 109 [menu_entry()]. 110 111menu_entry() -> 112 {?__(1,"Text"),text,?__(2,"Convert text to a 3D object"),[option]}. 113 114command({shape,{text,Ask}}, St) -> make_text(Ask, St); 115command(_, _) -> next. 116 117make_text(Ask, St) when is_atom(Ask) -> 118 FontDirs = sysfontdirs(), 119 DefFont = default_font(), 120 FontInfo = case wpa:pref_get(wpc_tt, fontname, DefFont) of 121 FI = #{type := font} -> FI; 122 _ -> DefFont 123 end, 124 125 Text = wpa:pref_get(wpc_tt, text, "Wings 3D"), 126 Bisect = wpa:pref_get(wpc_tt, bisections, 0), 127 GbtFonts = process_ttfs(FontDirs), 128 %% io:format("FontList: ~p\n\n",[gb_trees:to_list(GbtFonts)]), 129 Dlg = 130 [{vframe, [ 131 {hframe,[ 132 {label, ?__(2,"Text")}, 133 {text,Text,[{key,{wpc_tt,text}}]}, 134 help_button() 135 ]}, 136 {label_column, [ 137 {?__(5,"Number of edge bisections"), 138 {slider,{text,Bisect,[{key,{wpc_tt,bisections}},{range,{0,4}}]}}}, 139 {?__(3,"TrueType font"), 140 {fontpicker,FontInfo,[{key,{wpc_tt,font}}]}}]}, 141 wings_shapes:transform_obj_dlg() 142 ],[{margin,false}] 143 }], 144 Fun = fun({dialog_preview,[T,N,{_,Ctrl},RX,RY,RZ,MX,MY,MZ,Grnd]=_Res}) -> 145 {NewFontI, FPath} = find_font_file(GbtFonts,Ctrl), 146 Size = maps:get(size, NewFontI), 147 {preview,{shape,{text,[T,N,{fontdir,FPath}, 148 Size,RX,RY,RZ,MX,MY,MZ,Grnd]}},St}; 149 (cancel) -> 150 St; 151 ([T,N,{_,WxFont},RX,RY,RZ,MX,MY,MZ,Grnd]=_Res) when is_tuple(WxFont) -> 152 {NewFontI, FPath} = find_font_file(GbtFonts,WxFont), 153 Size = maps:get(size, NewFontI), 154 case FPath of 155 {_, undefined} -> 156 St; 157 _ -> 158 wpa:pref_set(wpc_tt, fontname, NewFontI), 159 wpa:pref_set(wpc_tt, text, element(2,T)), 160 wpa:pref_set(wpc_tt, bisections, element(2,N)), 161 {commit,{shape,{text,[T,N,{fontdir,FPath}, 162 Size,RX,RY,RZ,MX,MY,MZ,Grnd]}},St} 163 end 164 end, 165 wings_dialog:dialog(Ask,?__(1,"Create Text"), {preview, Dlg}, Fun); 166 167make_text([{_,T},{_,N},{_,FontFile}, Size|Transf], _) -> 168 %% Assuming 10 ppi is 2 w.u. which the other primitives are 169 gen(FontFile, T, N, Size*0.25, Transf). 170 171help_button() -> 172 Title = ?__(1,"Select font"), 173 TextFun = fun () -> help() end, 174 {help,Title,TextFun}. 175 176help() -> 177 [?__(1,"Only TrueType (OpenType) fonts can be used \n" 178 "and they must be installed in the standard operating system\n" 179 "directories for fonts.")]. 180 181gen(_FontFile, "", _Nsubsteps, _, _Transf) -> 182 keep; 183gen({FName,{{error, Reason, _}, _Idx}}, _, _, _, _) -> 184 Msg = ?__(1,"Text: ") ++ FName ++ " " ++ format_error(Reason), 185 wpa:error_msg(Msg); 186gen({FName,undefined}, _, _, _, _) -> 187 Msg = ?__(2,"Text: Failed to locate the TTF file or unknown format: ") ++ FName, 188 wpa:error_msg(Msg); 189gen({_FName, {File, Idx}}, Text, Nsubsteps, Size, Transf) -> 190 try trygen(File, Text, Idx, Nsubsteps, Size) of 191 {new_shape,Name,Fs0,Vs0,He} -> 192 Vs = wings_shapes:transform_obj(Transf, Vs0), 193 Fs = [#e3d_face{vs=Vsidx} || Vsidx <- Fs0], 194 Mesh = #e3d_mesh{type=polygon, vs=Vs, fs=Fs, he=He}, 195 {new_shape, Name, #e3d_object{obj=Mesh}, []}; 196 keep -> 197 keep 198 catch 199 throw:{error, _What, Msg}:_St -> 200 %% ?DBG("error: ~p ~p~n ~P~n", [_What, Msg, _St, 40]), 201 wpa:error_msg(Msg); 202 _:X:ST -> 203 io:format(?__(5,"caught error: ") ++"~P~nST:~p", [X, 40,ST]), 204 wpa:error_msg(?__(6,"Text failed: internal error")) 205 end. 206 207process_ttfs(Dirs) -> 208 case ets:info(?MODULE) of 209 undefined -> 210 Tab = ets:new(?MODULE, [named_table, public]), 211 Add = fun(FileName, _Acc) -> 212 Store = fun(FontInfo0, Idx) -> 213 {Key,_} = KV = 214 case FontInfo0 of 215 {error, Reason, FI} -> 216 {FI, {{error, Reason, FileName}, Idx}}; 217 FI -> 218 {FI, {FileName,Idx}} 219 end, 220 case ets:lookup(Tab, Key) of 221 [] -> ok; 222 [_Old] -> 223 %% ?DBG("Overwrite Font Info:~nOLD: ~p~nNEW: ~p~n", 224 %% [_Old,{FontInfo,{FileName,Idx}}]), 225 ok 226 end, 227 true = ets:insert(Tab, KV), 228 Idx+1 229 end, 230 try find_font_info(FileName) of 231 List -> 232 lists:foldl(Store, 0, List), 233 ok 234 catch _:_What:_St -> 235 ?DBG("Fail: ~p : ~P~n ~P~n",[FileName, _What, 20, _St, 20]), 236 ok 237 end 238 end, 239 Filter = ".ttf|.TTF|.ttc|.TTC|.otf|.OTF", 240 lists:foldl(fun(Dir, Tree) -> 241 filelib:fold_files(Dir, Filter, true, Add, Tree) 242 end, ok, Dirs), 243 Tab; 244 [_|_] -> 245 ?MODULE 246 end. 247 248trygen(File, Text, SubDiv) -> 249 trygen(File, Text, 0, SubDiv, 2). 250trygen(File, Text, Idx, Nsubsteps, Size) -> 251 TTF = init_font(File, Idx), 252 ?DBG("~P~n",[TTF, 20]), 253 Pa0 = get_polyareas(Text, TTF, Nsubsteps, Size), 254 {Vs0,Fs,He} = polyareas_to_faces(Pa0), 255 case Vs0 of 256 [_|_] -> 257 {CX,_CY,CZ} = e3d_vec:average(tuple_to_list(e3d_bv:box(Vs0))), 258 Vs = [{X-CX,Y,Z-CZ} || {X,Y,Z} <- Vs0], 259 {new_shape,"text: " ++ Text,Fs,Vs,He}; 260 [] -> 261 keep 262 end. 263 264%% Look up value with Name in Windows registry, 265%% first changing to key K under the "CurrentVersion" for Windows. 266%% Return value as string, or the token "none" if any problems. 267winregval(K, Name) -> 268 case os:type() of 269 {win32,Wintype} -> 270 case win32reg:open([read]) of 271 {ok, RH} -> 272 W = case Wintype of nt -> "Windows NT" ; _ -> "Windows" end, 273 CVK = "\\hklm\\SOFTWARE\\Microsoft\\" ++ W 274 ++ "\\CurrentVersion", 275 K1 = case K of 276 "" -> CVK; 277 _ -> CVK ++ "\\" ++ K 278 end, 279 Val = case win32reg:change_key(RH, K1) of 280 ok -> 281 case win32reg:value(RH, Name) of 282 {ok, V} -> V; 283 _ -> none 284 end; 285 _ -> none 286 end, 287 win32reg:close(RH), 288 Val; 289 _ -> none 290 end; 291 _ -> 292 none 293 end. 294 295%% Try to find default system directory for fonts 296sysfontdirs() -> 297 sysfontdirs(os:type()). 298 299sysfontdirs({win32,Wintype}) -> 300 Def = case Wintype of 301 nt -> "C:/winnt"; 302 _ -> "C:/windows" 303 end, 304 System = case winregval("", "SystemRoot") of 305 none -> Def; 306 Val -> Val 307 end, 308 UserInstalled = filename:join(filename:basedir(user_data, "Microsoft"), "Windows"), 309 [filename:join(System, "Fonts"), filename:join(UserInstalled, "Fonts")]; 310sysfontdirs({unix,darwin}) -> 311 Home = os:getenv("HOME"), 312 ["/Library/Fonts", "/System/Library/Fonts/", filename:join(Home, "Library/Fonts")]; 313sysfontdirs({unix,_}) -> 314 Home = os:getenv("HOME"), 315 ["/usr/share/fonts/", "/usr/local/share/fonts", 316 filename:join(Home, ".fonts"), 317 filename:join(Home, ".local/share/fonts")]. 318 319default_font() -> 320 wings_text:get_font_info(wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT)). 321 322%% Return {Vs,Fs} corresponding to list of polyareas, 323%% where Vs is list of coords and Fs is list of list of 324%% coord indices, describing faces. 325polyareas_to_faces(Pas) -> 326 VFpairs = map(fun pa2object/1, Pas), 327 concatvfs(VFpairs). 328 329concatvfs(Vfp) -> concatvfs(Vfp, 0, [], [], []). 330 331concatvfs([{Vs,Fs,HardEdges}|Rest], Offset, Vsacc, Fsacc, Hdacc) -> 332 Fs1 = offsetfaces(Fs, Offset), 333 He1 = offsetfaces(HardEdges, Offset), 334 Off1 = Offset + length(Vs), 335 concatvfs(Rest, Off1, [Vs|Vsacc], Fsacc ++ Fs1, He1 ++ Hdacc); 336concatvfs([], _Offset, Vsacc, Fsacc, Hdacc) -> 337 He = build_hard_edges(Hdacc, []), 338 {flatten(reverse(Vsacc)),Fsacc, He}. 339 340build_hard_edges([[First|_]=Loop|Rest], All) -> 341 New = build_hard_edges(Loop, First, All), 342 build_hard_edges(Rest, New); 343build_hard_edges([], All) -> All. 344 345build_hard_edges([A|[B|_]=Rest], First, All) -> 346 build_hard_edges(Rest, First, [{A,B}|All]); 347build_hard_edges([Last], First, All) -> 348 [{Last, First}|All]. 349 350%% ttf fonts start with an "offset subtable": 351%% uint32 - tag to mark as TTF (one of the 0,1,0,0; "true"; or "OTTO") 352%% uint16 - number of directory tables 353%% uint16 - search range: (maximum power of 2 <= numTables)*16 354%% uint16 - entry selector: log2(maximum power of 2 <= numTables) 355%% uint16 - range shift: numTables*16-searchRange 356 357is_font(<<1,0,0,0,?SKIP>>) -> true; %% Truetype 1 358is_font(<<"typ1",?SKIP>>) -> true; %% Truetype with type 1 font, not supported 359is_font(<<"OTTO",?SKIP>>) -> true; %% OpenType with CFF 360is_font(<<0,1,0,0,?SKIP>>) -> true; %% OpenType with 1.0 361is_font(_) -> false. 362 363%% is_ttfc(<<"ttcf", ?SKIP>>) -> true; 364%% is_ttfc(_) -> false. 365 366get_polyareas(Text, Font, Nsubsteps, Size) -> 367 Scale = scale_for_mapping_em_to_pixels(Font, Size), 368 Area = fun(CodePoint, {X,Acc}) -> 369 Glyph = find_glyph_index(Font, CodePoint), 370 {Advance, _} = get_glyph_h_metrics(Font, Glyph), 371 %% ?DBG("Char: ~c Glyph: ~w Advance: ~p*~p=~p~n", 372 %% [CodePoint, Glyph, Advance, Scale, Advance*Scale]), 373 Areas = get_polyarea(Glyph, X, Scale, Nsubsteps, Font), 374 %% ?DBG(" ~150.p~n", [Areas]), 375 {X+Advance, Areas ++ Acc} 376 end, 377 {_, Pas} = lists:foldl(Area, {0, []}, Text), 378 Pas. 379 380get_polyarea(Glyph, X, Scale, Nsubsteps, Font) -> 381 GlyphVs = get_glyph_shape(Font, Glyph), 382 VsCont = verts_to_point_lists(GlyphVs, Scale, Nsubsteps), 383 {_X0,_Y0,_X1,_Y1} = bb_box(VsCont), 384 %% io:format("~p:~p: ~p ~p~n",[?MODULE,?LINE, Scale, get_glyph_box(Font, Glyph)]), 385 386 Make = fun(Vs) -> make_edges(Vs, {Scale,Scale}, {X*Scale, 0}, []) end, 387 Edges0 = lists:map(Make, VsCont), 388 Edges = [Edge || [_|_] = Edge <- Edges0], %% Filter out empty lists 389 %% io:format("~p:~p: Edges: ~n ~w~n",[?MODULE,?LINE, Edges]), 390 findpolyareas(Edges). 391 392verts_to_point_lists(Vs, Scale, SubDiv) -> 393 F = {8.0/(Scale*math:pow(8,SubDiv)), SubDiv}, 394 lists:reverse(verts_to_points(Vs, {0.0,0.0}, F, [], [])). 395 396verts_to_points([#vertex{type=move,pos=Point}|Vs], _, F, Cont, All) -> 397 verts_to_points(Vs, Point, F, [Point], add_contour(Cont, All)); 398verts_to_points([#vertex{type=line,pos=Point}|Vs], _, F, Cont, All) -> 399 verts_to_points(Vs, Point, F, [Point|Cont], All); 400verts_to_points([#vertex{type=curve,pos=VP={PX,PY},c={CX,CY}}|Vs], 401 {X,Y}, {Limit,Level}=F, Cont0, All) -> 402 Cont = tesselate_curve(X,Y, CX,CY, PX,PY, Limit, Level, Cont0), 403 verts_to_points(Vs, VP, F, Cont, All); 404verts_to_points([#vertex{type=cubic,pos=VP={PX,PY},c={CX,CY}, c1={CX1,CY1}}|Vs], 405 {X,Y}, {Limit,Level}=F, Cont0, All) -> 406 Cont = tesselate_cubic(X,Y, CX,CY, CX1,CY1, PX,PY, Limit, Level, Cont0), 407 verts_to_points(Vs, VP, F, Cont, All); 408verts_to_points([], _, _, Cont, All) -> 409 add_contour(Cont, All). 410 411add_contour([], All) -> All; 412add_contour(Cont, All) -> 413 [lists:reverse(Cont)|All]. 414 415tesselate_curve(X0,Y0, X1,Y1, X2,Y2, Limit, Level, Cont0) when Level >= 0 -> 416 Mx = (X0 + 2*X1 + X2)/4.0, 417 My = (Y0 + 2*Y1 + Y2)/4.0, 418 %% Versus Directly Drawn Line 419 Dx = (X0+X2)/2.0 - Mx, 420 Dy = (Y0+Y2)/2.0 - My, 421 %% io:format(" ~p,~p ~p,~p => ~p > ~p~n",[X0,Y0,X1,Y1,Dx*Dx+Dy*Dy,Limit]), 422 if (Dx*Dx+Dy*Dy) > Limit -> 423 Cont1 = tesselate_curve(X0,Y0, (X0+X1)/2.0,(Y0+Y1)/2.0, Mx,My, Limit, Level-1, Cont0), 424 tesselate_curve(Mx,My, (X1+X2)/2.0,(Y1+Y2)/2.0, X2,Y2, Limit, Level-1, Cont1); 425 true -> 426 [{X2,Y2}|Cont0] 427 end; 428tesselate_curve(_X0,_Y0, _X1,_Y1, X2,Y2, _F, _Level, Cont) -> 429 [{X2,Y2}|Cont]. 430 431tesselate_cubic(X0,Y0, X1,Y1, X2,Y2, X3,Y3, Limit, Level, Cont0) when Level >= 0 -> 432 Dx0 = X1-X0, Dy0 = Y1-Y0, 433 Dx1 = X2-X1, Dy1 = Y2-Y1, 434 Dx2 = X3-X2, Dy2 = Y3-Y2, 435 Dx = X3-X0, Dy = Y3-Y0, 436 437 LL = math:sqrt(Dx0*Dx0+Dy0*Dy0)+math:sqrt(Dx1*Dx1+Dy1*Dy1)+math:sqrt(Dx2*Dx2+Dy2*Dy2), 438 SL = math:sqrt(Dx*Dx+Dy*Dy), 439 440 if (LL*LL-SL*SL) > Limit -> 441 X01 = (X0+X1)/2, Y01 = (Y0+Y1)/2, 442 X12 = (X1+X2)/2, Y12 = (Y1+Y2)/2, 443 X23 = (X2+X3)/2, Y23 = (Y2+Y3)/2, 444 445 Xa = (X01+X12)/2, Ya = (Y01+Y12)/2, 446 Xb = (X12+X23)/2, Yb = (Y12+Y23)/2, 447 448 Mx = (Xa+Xb)/2, My = (Ya+Yb)/2, 449 450 Cont1 = tesselate_cubic(X0,Y0, X01,Y01, Xa,Ya, Mx,My, Limit, Level-1, Cont0), 451 tesselate_cubic(Mx,My, Xb,Yb, X23,Y23, X3,Y3, Limit, Level-1, Cont1); 452 true -> 453 [{X3,Y3}|Cont0] 454 end; 455tesselate_cubic(_X0,_Y0, _X1,_Y1, _X2,_Y2, X3,Y3, _F, _Level, Cont) -> 456 [{X3,Y3}|Cont]. 457 458 459bb_box(ListOfLists) -> 460 MinMax = fun({X,Y}, {MinX,MinY,MaxX,MaxY}) -> 461 {min(X,MinX), min(Y,MinY), 462 max(X,MaxX), max(X,MaxY)} 463 end, 464 lists:foldl(fun(List, Acc) -> lists:foldl(MinMax, Acc, List) end, 465 {0.0,0.0, 0.0,0.0}, ListOfLists). 466 467make_edges([{JX,JY}|Rest=[{KX,KY}|_]], Scale = {ScX, ScY}, Shift = {ShX,ShY}, Eds) -> 468 Edge = {{JX * ScX + ShX, JY * ScY + ShY}, 469 {KX * ScX + ShX, KY * ScY + ShY}}, 470 case Edge of 471 {V,V} -> %% Remove zero size edges 472 make_edges(Rest, Scale, Shift, Eds); 473 _ -> 474 make_edges(Rest, Scale, Shift, [Edge|Eds]) 475 end; 476make_edges(_, _, _, Eds) -> 477 lists:reverse(Eds). 478 479%%% 480 481%% Cconts is list of "curved contours". 482%% Each curved contour is a list of cedges, representing a closed contour. 483%% This routine analyzes the contours and partitions them into polyareas, 484%% where each polyarea has a boundary (CCW oriented) and an optional list 485%% of contained islands (each CW oriented). 486findpolyareas(Cconts) -> 487 Areas = map(fun ccarea/1, Cconts), 488 {Cc,_Ar} = orientccw(Cconts, Areas), 489 Cct = list_to_tuple(Cc), 490 N = size(Cct), 491 Art = list_to_tuple(Areas), 492 Lent = list_to_tuple(map(fun length/1,Cc)), 493 Seqn = seq(1,N), 494 Cls = [ {{I,J},classifyverts(element(I,Cct),element(J,Cct))} 495 || I <- Seqn, J <- Seqn], 496 Clsd = gb_trees:from_orddict(Cls), 497 Cont = [ {{I,J},contains(I,J,Art,Lent,Clsd)} 498 || I <- Seqn, J <- Seqn], 499 Contd = gb_trees:from_orddict(Cont), 500 Assigned = gb_sets:empty(), 501 getpas(1,N,Contd,Cct,{[],Assigned}). 502 503getpas(I,N,Contd,Cct,{Pas,Ass}) when I > N -> 504 case length(gb_sets:to_list(Ass)) of 505 N -> 506 reverse(Pas); 507 _ -> 508 %% not all assigned: loop again 509 getpas(1,N,Contd,Cct,{Pas,Ass}) 510 end; 511getpas(I,N,Contd,Cct,{Pas,Ass}=Acc) -> 512 case gb_sets:is_member(I,Ass) of 513 true -> getpas(I+1,N,Contd,Cct,Acc); 514 _ -> 515 case isboundary(I,N,Contd,Ass) of 516 true -> 517 %% have a new polyarea with boundary = contour I 518 Ass1 = gb_sets:add(I,Ass), 519 {Isls,Ass2} = getisls(I,N,N,Contd,Ass1,Ass1,[]), 520 Cisls = map(fun (K) -> revccont(element(K,Cct)) end, Isls), 521 Pa = #polyarea{boundary=element(I,Cct), islands=Cisls}, 522 getpas(I+1,N,Contd,Cct,{[Pa|Pas],Ass2}); 523 _ -> getpas(I+1,N,Contd,Cct,Acc) 524 end 525 end. 526 527%% Return true if there is no unassigned J <= second arg, J /= I, 528%% such that contour J contains contour I. 529isboundary(_I,0,_Contd,_Ass) -> true; 530isboundary(I,I,Contd,Ass) -> isboundary(I,I-1,Contd,Ass); 531isboundary(I,J,Contd,Ass) -> 532 case gb_sets:is_member(J,Ass) of 533 true -> 534 isboundary(I,J-1,Contd,Ass); 535 _ -> 536 case gb_trees:get({J,I},Contd) of 537 true -> false; 538 _ -> isboundary(I,J-1,Contd,Ass) 539 end 540 end. 541 542%% Find islands for contour I : i.e., unassigned contours directly inside it. 543%% Only have to check J and less. 544%% Ass, Isls are (assigned-so-far, islands-so-far). 545%% Ass0 is assigned before we started adding islands. 546%% Return {list of island indices, Assigned array with those indices added} 547getisls(_I,0,_N,_Contd,_Ass0,Ass,Isls) -> {reverse(Isls),Ass}; 548getisls(I,J,N,Contd,Ass0,Ass,Isls) -> 549 case gb_sets:is_member(J,Ass) of 550 true -> 551 getisls(I,J-1,N,Contd,Ass0,Ass,Isls); 552 _ -> 553 case directlycont(I,J,N,Contd,Ass0) of 554 true -> 555 getisls(I,J-1,N,Contd,Ass0,gb_sets:add(J,Ass),[J|Isls]); 556 _ -> 557 getisls(I,J-1,N,Contd,Ass0,Ass,Isls) 558 end 559 end. 560 561directlycont(I,J,N,Contd,Ass) -> 562 gb_trees:get({I,J},Contd) andalso 563 foldl(fun (K,DC) -> 564 DC andalso 565 (K == J orelse gb_sets:is_member(K,Ass) orelse 566 not(gb_trees:get({K,J},Contd))) end, 567 true, seq(1,N)). 568 569ccarea(Ccont) -> 570 0.5 * foldl(fun ({{X1,Y1},{X2,Y2}},A) -> 571 A + X1*Y2 - X2*Y1 end, 572 0.0, Ccont). 573 574%% Reverse contours if area is negative (meaning they were Clockwise), 575%% and return revised Cconts and Areas. 576orientccw(Cconts, Areas) -> orientccw(Cconts, Areas, [], []). 577 578orientccw([], [], Cacc, Aacc) -> 579 { reverse(Cacc), reverse(Aacc) }; 580orientccw([C|Ct], [A|At], Cacc, Aacc) -> 581 if 582 A >= 0.0 -> 583 orientccw(Ct, At, [C|Cacc], [A|Aacc]); 584 true -> 585 orientccw(Ct, At, [revccont(C)|Cacc], [-A|Aacc]) 586 end. 587 588revccont(C) -> reverse(map(fun revcedge/1, C)). 589 590%% reverse a cedge 591revcedge({Vs,Ve}) -> {Ve,Vs}. 592 593%% classify vertices of contour B with respect to contour A. 594%% return {# inside A, # on A}. 595classifyverts(A,B) -> 596 foldl(fun({Vb,_},Acc) -> cfv(A,Vb,Acc) end, {0,0}, B). 597 598 599%% Decide whether vertex P is inside or on (as a vertex) contour A, 600%% and return modified pair. Assumes A is CCW oriented. 601%% CF Eric Haines ptinpoly.c in Graphics Gems IV 602cfv(A,P,{Inside,On}) -> 603 {Va0, _} = last(A), 604 if 605 Va0 == P -> 606 {Inside, On+1}; 607 true -> 608 Yflag0 = (element(2,Va0) > element(2,P)), 609 case vinside(A, Va0, P, false, Yflag0) of 610 true -> {Inside+1, On}; 611 false -> {Inside, On}; 612 on -> {Inside, On+1} 613 end 614 end. 615 616vinside([], _V0, _P, Inside, _Yflag0) -> 617 Inside; 618vinside([{{X1,Y1}=V1,_}|Arest], {X0,Y0}, P={Xp,Yp}, Inside, Yflag0) -> 619 if 620 V1 == P -> 621 on; 622 true -> 623 Yflag1 = (Y1 > Yp), 624 Inside1 = 625 if 626 Yflag0 == Yflag1 -> Inside; 627 true -> 628 Xflag0 = (X0 >= Xp), 629 Xflag1 = (X1 >= Xp), 630 if 631 Xflag0 == Xflag1 -> 632 case Xflag0 of 633 true -> not(Inside); 634 _ -> Inside 635 end; 636 true -> 637 Z = X1 - (Y1-Yp)*(X0-X1)/(Y0-Y1), 638 if 639 Z >= Xp -> not(Inside); 640 true -> Inside 641 end 642 end 643 end, 644 vinside(Arest, V1, P, Inside1, Yflag1) 645 end. 646 647%% I, J are indices into tuple Cct of curved contours. 648%% Clsd is gb_tree mapping {I,J} to [Inside,On,Outside]. 649%% Return true if contour I contains at least 55% of contour J's vertices. 650%% (This low percentage is partly because we are dealing with polygonal approximations 651%% to curves, sometimes, and the containment relation may seem worse than it actually is.) 652%% Lengths (in Lent tuple) are used for calculating percentages. 653%% Areas (in Art tuple) are used for tie-breaking. 654%% Return false if contour I is different from contour J, and not contained in it. 655%% Return same if I == J or all vertices on I are on J (duplicate contour). 656contains(I,I,_,_,_) -> 657 same; 658contains(I,J,Art,Lent,Clsd) -> 659 LenI = element(I,Lent), 660 LenJ = element(J,Lent), 661 {JinsideI,On} = gb_trees:get({I,J},Clsd), 662 if 663 JinsideI == 0 -> 664 false; 665 On == LenJ, LenI == LenJ -> 666 same; 667 true -> 668 if 669 float(JinsideI) / float(LenJ) > 0.55 -> 670 {IinsideJ,_} = gb_trees:get({J,I},Clsd), 671 FIinJ = float(IinsideJ) / float(LenI), 672 if 673 FIinJ > 0.55 -> 674 element(I,Art) >= element(J,Art); 675 true -> 676 true 677 end; 678 true -> 679 false 680 end 681 end. 682 683%% Return {Vs,Fs} where Vs is list of {X,Y,Z} for vertices 0, 1, ... 684%% and Fs is list of lists, each sublist is a face (CCW ordering of 685%% (zero-based) indices into Vs). 686pa2object(#polyarea{boundary=B,islands=Isls}) -> 687 Vslist = [cel2vec(B, 0.0) | map(fun (L) -> cel2vec(L, 0.0) end, Isls)], 688 Vtop = flatten(Vslist), 689 Vbot = map(fun ({X,Y,Z}) -> {X,Y,Z-0.2} end, Vtop), 690 Vs = Vtop ++ Vbot, 691 Nlist = [length(B) | map(fun (L) -> length(L) end, Isls)], 692 Ntot = sum(Nlist), 693 Fs1 = [FBtop | Holestop] = faces(Nlist,0,top), 694 Fs2 = [FBbot | Holesbot] = faces(Nlist,Ntot,bot), 695 Fsides = sidefaces(Nlist, Ntot), 696 FtopQ = e3d__tri_quad:quadrangulate_face_with_holes(FBtop, Holestop, Vs), 697 FbotQ = e3d__tri_quad:quadrangulate_face_with_holes(FBbot, Holesbot, Vs), 698 Ft = [ F#e3d_face.vs || F <- FtopQ ], 699 Fb = [ F#e3d_face.vs || F <- FbotQ ], 700 Fs = Ft ++ Fb ++ Fsides, 701 {Vs,Fs, [ F#e3d_face.vs || F <- Fs1 ++ Fs2]}. 702 703cel2vec(Cel, Z) -> map(fun({{X,Y},_}) -> {X,Y,Z} end, Cel). 704 705faces(Nlist,Org,Kind) -> faces(Nlist,Org,Kind,[]). 706 707faces([],_Org,_Kind,Acc) -> reverse(Acc); 708faces([N|T],Org,Kind,Acc) -> 709 FI = case Kind of 710 top -> #e3d_face{vs=seq(Org, Org+N-1)}; 711 bot -> #e3d_face{vs=seq(Org+N-1, Org, -1)} 712 end, 713 faces(T,Org+N,Kind,[FI|Acc]). 714 715sidefaces(Nlist,Ntot) -> sidefaces(Nlist,0,Ntot,[]). 716 717sidefaces([],_Org,_Ntot,Acc) -> append(reverse(Acc)); 718sidefaces([N|T],Org,Ntot,Acc) -> 719 End = Org+N-1, 720 Fs = [ [I, Ntot+I, wrap(Ntot+I+1,Ntot+Org,Ntot+End), wrap(I+1,Org,End)] 721 || I <- seq(Org, End) ], 722 sidefaces(T,Org+N,Ntot,[Fs|Acc]). 723 724%% I should be in range (Start, Start+1, ..., End). Make it so. 725wrap(I,Start,End) -> Start + ((I-Start) rem (End+1-Start)). 726 727offsetfaces(Fl, Offset) -> 728 map(fun (F) -> offsetface(F,Offset) end, Fl). 729 730offsetface(F, Offset) -> 731 map(fun (V) -> V+Offset end, F). 732 733%%%%%%%%%%%%%%%%%%%%% TTF PARSER %%%%%%%%%%%%%%%%%%%% 734 735 736%% Heavily inspired from Sean Barret's code @ nothings.org (see stb_truetype.h) 737%% 738%% @doc 739%% Codepoint 740%% Characters are defined by unicode codepoints, e.g. 65 is 741%% uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is 742%% the hiragana for "ma". 743%% 744%% Glyph 745%% A visual character shape (every codepoint is rendered as 746%% some glyph) 747%% 748%% Glyph index 749%% A font-specific integer ID representing a glyph 750%% 751%% Baseline 752%% Glyph shapes are defined relative to a baseline, which is the 753%% bottom of uppercase characters. Characters extend both above 754%% and below the baseline. 755%% 756%% Current Point 757%% As you draw text to the screen, you keep track of a "current point" 758%% which is the origin of each character. The current point's vertical 759%% position is the baseline. Even "baked fonts" use this model. 760%% 761%% Vertical Font Metrics 762%% The vertical qualities of the font, used to vertically position 763%% and space the characters. See docs for get_font_v_metrics. 764%% 765%% Font Size in Pixels or Points 766%% The preferred interface for specifying font sizes in truetype 767%% is to specify how tall the font's vertical extent should be in pixels. 768%% If that sounds good enough, skip the next paragraph. 769%% 770%% Most font APIs instead use "points", which are a common typographic 771%% measurement for describing font size, defined as 72 points per inch. 772%% truetype provides a point API for compatibility. However, true 773%% "per inch" conventions don't make much sense on computer displays 774%% since they different monitors have different number of pixels per 775%% inch. For example, Windows traditionally uses a convention that 776%% there are 96 pixels per inch, thus making 'inch' measurements have 777%% nothing to do with inches, and thus effectively defining a point to 778%% be 1.333 pixels. Additionally, the TrueType font data provides 779%% an explicit scale factor to scale a given font's glyphs to points, 780%% but the author has observed that this scale factor is often wrong 781%% for non-commercial fonts, thus making fonts scaled in points 782%% according to the TrueType spec incoherently sized in practice. 783%% 784 785%% Each .ttf/.ttc file may have more than one font. Each font has a 786%% sequential index number starting from 0. A regular .ttf file will 787%% only define one font and it always be at index 0. 788 789platform(0) -> unicode; 790platform(1) -> mac; 791platform(2) -> iso; 792platform(3) -> microsoft; 793platform(Id) -> Id. 794 795encoding(0, unicode) -> {unicode, {1,0}}; 796encoding(1, unicode) -> {unicode, {1,1}}; 797encoding(2, unicode) -> iso_10646; 798encoding(3, unicode) -> {unicode, bmp, {2,0}}; 799encoding(4, unicode) -> {unicode, full,{2,0}}; 800encoding(5, unicode) -> {unicode_nyi, format_14}; 801 802encoding(0, microsoft) -> symbol; 803encoding(1, microsoft) -> {unicode, bmp}; 804encoding(2, microsoft) -> shiftjis; 805encoding(10, microsoft) -> {unicode, bmp}; 806 807encoding(0, mac) -> roman ; 808encoding(1, mac) -> japanese ; 809encoding(2, mac) -> chinese_trad ; 810encoding(3, mac) -> korean ; 811encoding(4, mac) -> arabic ; 812encoding(5, mac) -> hebrew ; 813encoding(6, mac) -> greek ; 814encoding(7, mac) -> russian ; 815 816encoding(Id, _) -> Id. 817 818language(0 , mac) -> english ; 819language(12, mac) -> arabic ; 820language(4 , mac) -> dutch ; 821language(1 , mac) -> french ; 822language(2 , mac) -> german ; 823language(10, mac) -> hebrew ; 824language(3 , mac) -> italian ; 825language(11, mac) -> japanese; 826language(23, mac) -> korean ; 827language(32, mac) -> russian ; 828language(6 , mac) -> spanish ; 829language(5 , mac) -> swedish ; 830language(33, mac) -> chinese_simplified ; 831language(19, mac) -> chinese ; 832 833language(16#0409, microsoft) -> english ; 834language(16#0804, microsoft) -> chinese ; 835language(16#0413, microsoft) -> dutch ; 836language(16#040c, microsoft) -> french ; 837language(16#0407, microsoft) -> german ; 838language(16#040d, microsoft) -> hebrew ; 839language(16#0410, microsoft) -> italian ; 840language(16#0411, microsoft) -> japanese; 841language(16#0412, microsoft) -> korean ; 842language(16#0419, microsoft) -> russian ; 843%%language(16#0409, microsoft) -> spanish ; 844language(16#041d, microsoft) -> swedish ; 845language(Id, _) -> Id. 846 847info(0) -> copyright; 848info(1) -> family; 849info(2) -> subfamily; 850info(3) -> unique_subfamily; 851info(4) -> fullname; 852info(5) -> version; 853info(6) -> postscript_name; 854info(7) -> trademark_notice; 855info(8) -> manufacturer_name; 856info(9) -> designer; 857info(10) -> description; 858info(11) -> url_vendor; 859info(12) -> url_designer; 860info(13) -> license_descr; 861info(14) -> url_license; 862%info(15) -> reserved; 863info(16) -> preferred_family; 864info(17) -> preferred_subfamily; 865%%info(18) -> compatible_full; %% Mac only 866info(19) -> sample_text; 867info(Id) -> Id. 868 869-spec init_font(FileName, Index) -> ttf() when 870 FileName :: list(), 871 Index :: integer(). 872init_font(Filename, Index) -> 873 case file:read_file(Filename) of 874 {ok, Bin} -> init_font_1(Filename, Bin, Index); 875 {error,Error} -> throw({error, Error, "Couldn't open file" ++ Filename}) 876 end. 877 878init_font_1(Filename, Bin0, Index) -> 879 Bin = get_font_from_offset(Bin0, Index), 880 is_font(Bin) orelse throw({error, bad_ttf_file}), 881 Tabs = find_tables(Bin), 882 Name = case maps:get(<<"name">>, Tabs, undefined) of 883 undefined -> throw({error, bad_ttf_file}); 884 NameData -> NameData 885 end, 886 Os2 = maps:get(<<"OS/2">>, Tabs, undefined), 887 try 888 CMap = maps:get(<<"cmap">>, Tabs), 889 %% Either loca and glyf 890 Loca = maps:get(<<"loca">>, Tabs, undefined), 891 Glyf = maps:get(<<"glyf">>, Tabs, undefined), 892 %% or CFF is needed 893 Cff = maps:get(<<"CFF ">>, Tabs, undefined), 894 Head = maps:get(<<"head">>, Tabs), 895 Hhea = maps:get(<<"hhea">>, Tabs), 896 Hmtx = maps:get(<<"hmtx">>, Tabs), 897 Kern = maps:get(<<"kern">>, Tabs, undefined), 898 NumGlyphs = num_glyphs(maps:get(<<"maxp">>, Tabs, undefined), Bin0), 899 IndexMap = find_index_map(CMap, Bin0), 900 CffMap = pp_cff(Cff, Bin0, Filename), 901 (Loca == undefined orelse Glyf == undefined) 902 andalso CffMap == undefined 903 andalso throw({error, no_glyf_info}), 904 Skip = Head+50, 905 <<_:Skip/binary, LocFormat:?U16, ?SKIP>> = Bin0, 906 #ttf_info{data = Bin0, file = Filename, collection = Index, 907 name = Name, os2 = Os2, 908 num_glyphs = NumGlyphs, 909 loca = Loca, glyf = Glyf, 910 cff = CffMap, 911 head = Head, hhea = Hhea, 912 hmtx = Hmtx, kern = Kern, 913 index_map = IndexMap, 914 index_to_loc_format = LocFormat 915 } 916 catch error:_Err:_ST -> 917 %% We create a bad tff_info here to give other user error messages 918 %% than file not found 919 io:format("Parse error: ~p~n~P:~n ~P~n",[Filename, _Err,30,_ST, 100]), 920 throw({error, parse_error, 921 #ttf_info{name=Name, os2=Os2, data=Bin0, 922 file = {error, parse_error, Filename}}}); 923 throw:{error,_Err} -> 924 throw({error,_Err, 925 #ttf_info{name=Name, os2=Os2, data=Bin0, 926 file = {error, _Err, Filename}}}) 927 end. 928 929format_error(Error) -> 930 io:format("TFF error: ~p~n", [Error]), 931 ?__(1,"Unsupported ttf format"). 932 933find_font_info(File) -> 934 {ok,Filecontents} = file:read_file(File), 935 find_font_info(Filecontents, File). 936 937find_font_info(<<"ttcf", 0,_V,0,0, N:32, ?SKIP >> = Bin, File) -> 938 %% ?DBG("Version ~w: Size ~w~n",[V,N]), 939 Info = fun(Idx, Acc) -> 940 try 941 Font = init_font_1(File, Bin, Idx), 942 [find_font_info_1(Font)|Acc] 943 catch throw:_Reason -> 944 ?DBG("~s:~w: ~p~n", [File, Idx, _Reason]), 945 Acc 946 end 947 end, 948 lists:foldl(Info, [], lists:seq(0,N-1)); 949find_font_info(Bin, File) -> 950 try init_font_1(File, Bin,0) of 951 Font -> 952 [find_font_info_1(Font)] 953 catch throw:{error, Reason, #ttf_info{}=Font} -> 954 ?DBG("~s: ~p~n", [File, Reason]), 955 [{error, Reason, find_font_info_1(Font)}] 956 end. 957 958find_font_info_1(#ttf_info{file=_File, collection=_Coll} = TTF) -> 959 FontInfo = font_info(TTF), 960 Family = proplists:get_value(family, FontInfo, undefined), 961 PrefFamily = proplists:get_value(preferred_family, FontInfo, undefined), 962 {Style, Weight} = font_styles(TTF), 963 %% ?DBG("~p (~w) ~p ~s ~s~n ~0.p~n~n", [_File, _Coll, Family, Style, Weight, FontInfo]), 964 %% io:format("File: ~p ~p ~p ~p ~p~n", [_File,Family, PrefFamily, Style, Weight]), 965 case PrefFamily of 966 undefined -> {Family, Family,Style,Weight}; 967 _ -> {Family, PrefFamily, Style, Weight} 968 end. 969 970check_enc(A, A) -> true; 971check_enc({unicode,_}, unicode) -> true; 972check_enc({unicode,_,_}, unicode) -> true; 973check_enc(_, _) -> false. 974 975string(String, roman) -> 976 unicode:characters_to_list(String, latin1); 977string(String, {unicode, _}) -> 978 unicode:characters_to_list(String, utf16); 979string(String, {unicode, bmp, _}) -> 980 unicode:characters_to_list(String, utf16); 981string(String, {unicode, full, _}) -> 982 unicode:characters_to_list(String, utf32); 983string(String, _) -> 984 String. 985 986font_styles(#ttf_info{data=Bin, os2=TabOffset}) when is_integer(TabOffset) -> 987 <<_:TabOffset/binary,_Ver:16,_:16,Weight:?U16,_Pad:26/binary, 988 _Panose:10/binary,_ChrRng:16/binary,_VenId:4/binary, 989 FsSel:16,_T1/binary>> = Bin, 990 FStyle = if (FsSel band ?fsITALIC) =:= ?fsITALIC -> italic; 991 true -> normal 992 end, 993 FWeight = if 994 Weight < 150 -> light; %% thin 995 Weight < 250 -> light; %% extra-light 996 Weight < 350 -> light; 997 Weight < 450 -> normal; 998 Weight < 550 -> normal; %% medium 999 Weight < 650 -> bold; %% semi-bold 1000 Weight < 750 -> bold; 1001 Weight < 850 -> bold; %% extra-bold 1002 true -> bold %% black 1003 end, 1004 {FStyle, FWeight}; 1005font_styles(_) -> 1006 {normal, normal}. 1007 1008%% Return the requested string from font 1009%% By default font family and subfamily (if not regular) 1010font_info(Font) -> 1011 StdInfoItems = [info(1),info(2),info(3),info(4),info(16),info(17)], 1012 Try = [{StdInfoItems, microsoft, unicode, english}, 1013 {StdInfoItems, unicode, unicode, 0}, 1014 {StdInfoItems, mac, roman, english} 1015 ], 1016 font_info_2(Font, Try). 1017 1018font_info_2(Font, [{Id,Platform,Enc,Lang}|Rest]) -> 1019 case font_info(Font, Id, Platform, Enc, Lang) of 1020 [] -> font_info_2(Font, Rest); 1021 Info -> Info 1022 end. 1023 1024%% Return the requested string from font 1025%% Info Items: 1,2,3,4,16,17 may be interesting 1026%% Returns a list if the encoding is known otherwise a binary. 1027%% Return the empty list is no info that could be matched is found. 1028-spec font_info(Font::ttf(), 1029 [InfoId::integer()], 1030 Platform::platform(), 1031 Encoding::encoding(), 1032 Language::language()) -> [{InfoId::integer, string()}]. 1033font_info(#ttf_info{data=Bin, name=Name}, Id, Platform, Encoding, Language) -> 1034 <<_:Name/binary, _V:16, Count:?U16, StringOffset:?U16, FI/binary>> = Bin, 1035 <<_:Name/binary, _:StringOffset/binary, Strings/binary>> = Bin, 1036 get_font_info(Count, FI, Strings, Id, Platform, Encoding, Language). 1037 1038get_font_info(0, _, _, _, _, _, _) -> []; 1039get_font_info(N, <<PId:?U16, EId:?U16, LId:?U16, NId:?U16, 1040 Length:?U16, StrOffset:?U16, Rest/binary>>, Strings, 1041 WIds, WPlatform, WEnc, WLang) -> 1042 <<_:StrOffset/binary, String:Length/binary, ?SKIP>> = Strings, 1043 Platform = platform(PId), 1044 Encoding = encoding(EId, Platform), 1045 Lang = language(LId, Platform), 1046 Enc = check_enc(Encoding, WEnc), 1047 case lists:member(info(NId), WIds) of 1048 true when Platform =:= WPlatform, Enc, Lang =:= WLang -> 1049 [{info(NId), string(String, Encoding)}| 1050 get_font_info(N-1, Rest, Strings, WIds, WPlatform, WEnc, WLang)]; 1051 _ -> 1052 get_font_info(N-1, Rest, Strings, WIds, WPlatform, WEnc, WLang) 1053 end. 1054 1055get_font_from_offset(<<"ttcf", 0,_V,0,0, N:32, Rest0/binary >> = Bin, Index) 1056 when N > Index -> 1057 Pos = Index*4, 1058 <<_:Pos/binary, Offset:32, ?SKIP>> = Rest0, 1059 <<_:Offset/binary, TTF/binary>> = Bin, 1060 TTF; 1061get_font_from_offset(Bin, 0) -> 1062 Bin. 1063 1064find_font_file(Table, WxFont) -> 1065 FontInfo = wings_text:get_font_info(WxFont), 1066 try 1067 #{face:=FName, style:=FStyle, weight:=FWeight} = FontInfo, 1068 Alternatives = find_font_file_0(Table, FName, true), 1069 ?DBG("~p => ~p~n", [FontInfo, Alternatives]), 1070 File = select_fontfile(Alternatives, FStyle, FWeight), 1071 ?DBG("FontFile: ~p~n", [File]), 1072 {FontInfo, {FName, File}} 1073 catch _:Er:St -> 1074 io:format("~p: ~p~n",[Er,St]), 1075 {FontInfo, undefined} 1076 end. 1077 1078find_font_file_0(Tab, [$@|FName], TryWin) -> 1079 %% Some fonts in windows start with a '@' do know why 1080 %% but they can't be found so remove '@' 1081 find_font_file_0(Tab, FName, TryWin); 1082find_font_file_0(Tab, FName, TryWin) -> 1083 case ets:match_object(Tab, {{FName,'_', '_', '_'}, '_'}) of 1084 [] -> 1085 case ets:match_object(Tab, {{'_', FName, '_', '_'},'_'}) of 1086 [] when TryWin -> 1087 find_font_file_1(Tab, FName); 1088 List -> 1089 List 1090 end; 1091 List -> 1092 List 1093 end. 1094 1095find_font_file_1(Table,FName) -> 1096 case winregval("FontSubstitutes",FName) of 1097 none -> []; 1098 FSName -> find_font_file_0(Table, FSName, false) 1099 end. 1100 1101select_fontfile(Alts0, Style, Weight) -> 1102 Alts = case [FI || {{_,_,S,_}, _} = FI <- Alts0, S =:= Style] of 1103 [] -> 1104 case [FI || {{_,_,normal,_}, _} = FI <- Alts0] of 1105 [] -> Alts0; 1106 As -> As 1107 end; 1108 As -> As 1109 end, 1110 select_fontfile_1(Alts, Weight). 1111 1112select_fontfile_1(Alts0, Weight) -> 1113 Alts = case [FI || {{_,_,_, W}, _} = FI <- Alts0, W =:= Weight] of 1114 [] -> 1115 case [FI || {{_,_,_,normal}, _} = FI <- Alts0] of 1116 [] -> Alts0; 1117 As -> As 1118 end; 1119 As -> As 1120 end, 1121 select_fontfile_2(Alts). 1122 1123select_fontfile_2([]) -> 1124 undefined; 1125select_fontfile_2([{_, File}|R] = _Alts) -> 1126 R =/= [] andalso ?DBG("Selecting hd of ~p~n",[_Alts]), 1127 File. 1128 1129 1130%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1131 1132find_tables(<<_:32, NumTables:?U16, _SR:16, _ES:16, _RS:16, Tables/binary>>) -> 1133 find_table(NumTables, Tables, []). 1134 1135find_table(0, _, Tabs) -> maps:from_list(Tabs); 1136find_table(Num, <<Tag:4/binary, _CheckSum:32, Offset:32, _Len:32, Next/binary>>, Tabs) -> 1137 find_table(Num-1, Next, [{Tag, Offset}|Tabs]). 1138 1139num_glyphs(undefined, _Bin) -> 1140 16#ffff; 1141num_glyphs(Offset0, Bin) -> 1142 Offset = Offset0+4, 1143 <<_:Offset/binary, NG:?U16, ?SKIP>> = Bin, 1144 NG. 1145 1146%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1147 1148find_index_map(Cmap, Bin) -> 1149 <<_:Cmap/binary, _:16, NumTables:?U16, Data/binary>> = Bin, 1150 case find_index_map1(NumTables, Data, []) of 1151 [] -> throw({error, supported_index_map_not_found}); 1152 Alternatives -> 1153 [{_, Offset}|_] = lists:sort(Alternatives), 1154 %% ?DBG("Index maps: ~p + ~p => ~p~n", [Cmap,lists:sort(Alternatives),Cmap + Offset]), 1155 Cmap + Offset 1156 end. 1157 1158find_index_map1(0, _, Res) -> Res; 1159find_index_map1(N, <<?PLATFORM_ID_MICROSOFT:?U16, Enc:?U16, Offset:?U32, Rest/binary>>, Prev) -> 1160 case Enc of 1161 ?MS_EID_UNICODE_BMP -> 1162 find_index_map1(N-1, Rest, [{5, Offset}|Prev]); 1163 ?MS_EID_UNICODE_FULL -> 1164 find_index_map1(N-1, Rest, [{1, Offset}|Prev]); 1165 _ -> %% For example ?MS_EID_SYMBOL 1166 ?DBG("Ignored: ~w ~p~n",[Enc, encoding(Enc, microsoft)]), 1167 find_index_map1(N-1, Rest, Prev) 1168 end; 1169find_index_map1(N, <<?PLATFORM_ID_UNICODE:?U16, Enc:?U16, Offset:?U32, Rest/binary>>, Prev) -> 1170 case Enc of 1171 5 -> %% Cmap format 14 (we don't support that) 1172 ?DBG("Ignored: ~w ~p~n",[Enc, encoding(Enc, unicode)]), 1173 find_index_map1(N-1, Rest, Prev); 1174 4 -> 1175 find_index_map1(N-1, Rest, [{0, Offset}|Prev]); 1176 3 -> 1177 find_index_map1(N-1, Rest, [{4, Offset}|Prev]); 1178 6 -> 1179 find_index_map1(N-1, Rest, [{2, Offset}|Prev]); 1180 _ -> 1181 find_index_map1(N-1, Rest, [{6, Offset}|Prev]) 1182 end; 1183find_index_map1(NumTables, <<_:64, Next/binary>>, Res) -> 1184 find_index_map1(NumTables-1, Next, Res). 1185 1186%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1187 1188%% Converts UnicodeCodePoint to Glyph index 1189%% Glyph 0 is the undefined glyph 1190-spec find_glyph_index(Font::ttf(), Char::integer()) -> Glyph::integer(). 1191find_glyph_index(#ttf_info{data=Bin, index_map=IndexMap, os2=_Os2}, UnicodeCP) -> 1192 <<_:IndexMap/binary, Fmt:?U16, Data/binary>> = Bin, 1193 %% ?DBG("Index map format: ~w @~w~n",[Fmt, IndexMap]), 1194 1195 %% <<_:_Os2/binary,_Ver:16,_:16,_Weight:?U16,_Pad:26/binary, 1196 %% _Panose:10/binary,R1:32,R2:32,R3:32,R4:32,?SKIP>> = Bin, 1197 %% ?DBG("Support: ~.2b ~.2b ~.2b ~.2b~n",[R1, R2, R3, R4]), 1198 1199 find_glyph_index(Fmt, Data, UnicodeCP). 1200 1201find_glyph_index(0, IndexMap, UnicodeCP) -> 1202 <<Bytes:?U16, _:16, _:UnicodeCP/binary, Index:8, ?SKIP>> = IndexMap, 1203 %% Format0: Apple byte encoding 1204 case UnicodeCP < (Bytes-6) of 1205 true -> Index; 1206 false -> ?DBG("No CP in range",[]), 0 1207 end; 1208find_glyph_index(4, IndexMap, UnicodeCP) -> 1209 %% Format4: 16 bit mapping 1210 <<_Len:16, _Lan:16, Format4/binary>> = IndexMap, 1211 format_4_index(Format4, UnicodeCP); 1212find_glyph_index(6, IndexMap, UnicodeCP) -> 1213 %% Format6: Dense 16 bit mapping 1214 <<_Len:16, _Lang:16, First:?U16, Count:?U16, IndexArray/binary>> = IndexMap, 1215 case UnicodeCP >= First andalso UnicodeCP < (First+Count) of 1216 false -> ?DBG("No CP in index range",[]), 0; 1217 true -> 1218 Pos = (UnicodeCP - First)*2, 1219 <<_:Pos/binary, Index:?U16, ?SKIP>> = IndexArray, 1220 Index 1221 end; 1222find_glyph_index(Format, IndexMap, UnicodeCP) 1223 when Format =:= 12; Format =:= 13 -> 1224 %% Format12/13: Mixed 16/32 and pure 32 bit mappings 1225 <<_:16, _:32, _:32, Count:?U32, Groups/binary>> = IndexMap, 1226 format_32_search(0, Count, Groups, UnicodeCP, Format); 1227find_glyph_index(_Format, _IndexMap, _UnicodeCP) -> 1228 %% Format2: Mixed 8/16 bits mapping for Japanese, Chinese and Korean 1229 %% Format8: Mixed 16/32 and pure 32 bit mappings 1230 %% Format10: Mixed 16/32 and pure 32 bit mappings 1231 ?DBG("unsupported glyph format ~w~n",[_Format]), 1232 0. 1233 1234format_4_index(_, Unicode) when Unicode >= 16#FFFF -> 0; 1235format_4_index(<<SegCountX2:?U16, SearchRange0:?U16, EntrySel:?U16, 1236 RangeShift:?U16, Table/binary>>, Unicode) -> 1237 %% SegCount = SegCountX2 div 2, 1238 SearchRange = SearchRange0 div 2, 1239 %% Binary Search 1240 <<EndCode:SegCountX2/binary, 0:16, 1241 StartCode:SegCountX2/binary, 1242 IdDelta:SegCountX2/binary, 1243 IdRangeOffset/binary %% Also includes GlyphIndexArray/binary 1244 >> = Table, 1245 1246 %% Ranges = lists:zip([Code || << Code:?U16 >> <= StartCode], 1247 %% [Code || << Code:?U16 >> <= EndCode]), 1248 %% Chars = [B-A+1 || {A,B} <- Ranges], 1249 %% ?DBG("~w~n",[Ranges]), 1250 %% ?DBG("~p ~w ~n",[lists:sum(Chars), Chars]), 1251 1252 %% they lie from endCount .. endCount + segCount 1253 %% but searchRange is the nearest power of two, so... 1254 RangeShift = SegCountX2 - SearchRange0, 1255 Search = case EndCode of 1256 <<_:RangeShift/binary, Search0:?U16, ?SKIP>> 1257 when Unicode >= Search0 -> 1258 RangeShift; 1259 _ -> 0 1260 end, 1261 Item = format_4_search(EntrySel, Search-2, SearchRange, EndCode, Unicode), 1262 case EndCode of 1263 <<_:Item/binary, Assert:16, ?SKIP>> -> 1264 true = Unicode =< Assert; 1265 _ -> exit(assert) 1266 end, 1267 <<_:Item/binary, Start:?U16, ?SKIP>> = StartCode, 1268 %% <<_:Item/binary, End:?U16, ?SKIP>> = EndCode, 1269 <<_:Item/binary, Offset:?U16, ?SKIP>> = IdRangeOffset, 1270 if 1271 Unicode < Start -> 1272 %% ?DBG("Unicode: ~w start ~w~n",[Unicode, Start]), 1273 0; 1274 Offset =:= 0 -> 1275 <<_:Item/binary, Index:?S16, ?SKIP>> = IdDelta, 1276 (Index + Unicode) rem 65536; 1277 true -> 1278 Skip = Item + Offset + (Unicode - Start)*2, 1279 <<_:Skip/binary, Index:?U16, ?SKIP>> = IdRangeOffset, 1280 Index 1281 end. 1282 1283format_4_search(EntrySel, Start, SearchRange, Bin, Unicode) when EntrySel > 0 -> 1284 Index = Start + SearchRange, 1285 case Bin of 1286 <<_:Index/binary, End:?U16, ?SKIP>> when Unicode > End -> 1287 format_4_search(EntrySel-1, Start+SearchRange, SearchRange div 2, Bin, Unicode); 1288 _ -> 1289 format_4_search(EntrySel-1, Start, SearchRange div 2, Bin, Unicode) 1290 end; 1291format_4_search(_, Search, _, _, _) -> 1292 Search+2. 1293 1294format_32_search(Low, High, Groups, UnicodeCP, Format) 1295 when Low < High -> 1296 Mid = Low + ((High - Low) div 2), 1297 MidIndex = Mid*12, 1298 <<_:MidIndex/binary, Start:?U32, End:?U32, Glyph:?U32, ?SKIP>> = Groups, 1299 if 1300 UnicodeCP < Start -> 1301 format_32_search(Low, Mid, Groups, UnicodeCP, Format); 1302 UnicodeCP > End -> 1303 format_32_search(Mid+1, High, Groups, UnicodeCP, Format); 1304 Format =:= 12 -> 1305 Glyph+UnicodeCP-Start; 1306 Format =:= 13 -> 1307 Glyph 1308 end; 1309format_32_search(_, _, _, _, _) -> 0. 1310 1311%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1312 1313get_glyf_offset(#ttf_info{num_glyphs=NumGlyphs}, Glyph) 1314 when Glyph > NumGlyphs -> 1315 ?DBG("Out of range ~p max: ~p~n",[Glyph, NumGlyphs]), 1316 -1; 1317get_glyf_offset(#ttf_info{index_to_loc_format=0, data=Bin, loca=Loca, glyf=Glyf}, Glyph) -> 1318 Skip = Glyph*2, 1319 <<_:Loca/binary, _:Skip/binary, G1:?U16, G2:?U16, ?SKIP>> = Bin, 1320 case G1 == G2 of 1321 true -> -1; 1322 false -> Glyf + G1 * 2 1323 end; 1324get_glyf_offset(#ttf_info{index_to_loc_format=1, data=Bin, loca=Loca, glyf=Glyf}, Glyph) -> 1325 Skip = Glyph*4, 1326 <<_:Loca/binary, _:Skip/binary, G1:?U32, G2:?U32, ?SKIP>> = Bin, 1327 case G1 == G2 of 1328 true -> -1; %% Length is zero 1329 false -> Glyf + G1 1330 end; 1331get_glyf_offset(#ttf_info{index_to_loc_format=_F}, _) -> 1332 ?DBG("unknown glyph map format: ~p~n",[_F]), 1333 -1. 1334 1335%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1336 1337%% leftSideBearing is the offset from the current horizontal position 1338%% to the left edge of the character advanceWidth is the offset from 1339%% the current horizontal position to the next horizontal position 1340%% these are expressed in unscaled coordinates 1341-spec get_glyph_h_metrics(Font::ttf(), Glyph::integer()) -> 1342 { Advance::integer(), 1343 LeftSideBearing::integer()}. 1344get_glyph_h_metrics(#ttf_info{data=Bin, hhea=Hhea, hmtx=Hmtx}, Glyph) -> 1345 <<_:Hhea/binary, _:34/binary, LongHorMetrics:?U16, ?SKIP>> = Bin, 1346 case Glyph < LongHorMetrics of 1347 true -> 1348 Skip = 4*Glyph, 1349 <<_:Hmtx/binary, _:Skip/binary, Advance:?S16, LeftSideBearing:?S16, ?SKIP>> = Bin, 1350 {Advance, LeftSideBearing}; 1351 false -> 1352 Skip1 = 4*(LongHorMetrics-1), 1353 <<_:Hmtx/binary, _:Skip1/binary, Advance:?S16, ?SKIP>> = Bin, 1354 Skip2 = 4*LongHorMetrics+2*(Glyph-LongHorMetrics), 1355 <<_:Hmtx/binary, _:Skip2/binary, LeftSideBearing:?S16, ?SKIP>> = Bin, 1356 {Advance, LeftSideBearing} 1357 end. 1358 1359%% Computes a scale factor to produce a font whose EM size is mapped to 1360%% 'pixels' tall. 1361-spec scale_for_mapping_em_to_pixels(Font::ttf(), Size::number()) -> Scale::float(). 1362scale_for_mapping_em_to_pixels(#ttf_info{data=Bin, head=Head}, Size) -> 1363 <<_:Head/binary, _:18/binary, UnitsPerEm:?U16, ?SKIP>> = Bin, 1364 Size / UnitsPerEm. 1365 1366%% -spec get_glyph_box(Font::ttf(), Glyph::integer()) -> 1367%% {X0::integer(),Y0::integer(), 1368%% X1::integer(),Y1::integer()}. 1369%% get_glyph_box(TTF = #ttf_info{data=Bin, glyf=Glyf}, Glyph) 1370%% when Glyf =/= undefined -> 1371%% case get_glyf_offset(TTF, Glyph) of 1372%% Offset when Offset > 0 -> 1373%% <<_:Offset/binary, _:16, X0:?S16, Y0:?S16, X1:?S16, Y1:?S16, ?SKIP>> = Bin, 1374%% {X0,Y0,X1,Y1}; 1375%% _ -> 1376%% {0,0,0,0} 1377%% end. 1378 1379 1380%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1381 1382-spec get_glyph_shape(Font::ttf(), Glyph::integer()) -> [Vertices::vertex()]. 1383get_glyph_shape(#ttf_info{glyf=undefined}=TTF, Glyph) -> 1384 get_glyph_shape_tt2(TTF, Glyph); 1385get_glyph_shape(TTF, Glyph) -> 1386 get_glyph_shape_tt(TTF, Glyph, get_glyf_offset(TTF, Glyph)). 1387 1388get_glyph_shape_tt(_TTF, _Glyph, Offset) 1389 when Offset < 0 -> []; 1390get_glyph_shape_tt(TTF = #ttf_info{data=Bin}, _Glyph, Offset) -> 1391 <<_:Offset/binary, NumberOfContours:?S16, 1392 _XMin:16, _YMin:16, _XMax:16, _YMax:16, 1393 GlyphDesc/binary>> = Bin, 1394 1395 %% ?DBG("Glyph: ~p ~p c#~p ~p ~p~n",[_Glyph, Offset, NumberOfContours, {_XMin,_XMax}, {_YMin,_YMax}]), 1396 1397 if NumberOfContours > 0 -> 1398 %% Single Glyph 1399 Skip = NumberOfContours*2 - 2, 1400 <<_:Skip/binary, Last:?U16, InsLen:?U16, Instr/binary>> = GlyphDesc, 1401 N = 1 + Last, 1402 <<_:InsLen/binary, FlagsBin/binary>> = Instr, 1403 %%io:format("Conts ~p ~p ~p~n",[NumberOfContours, InsLen, N]), 1404 {Flags, XCoordsBin} = parse_flags(N, 0, FlagsBin, []), 1405 {XCs, YCoordsBin} = parse_coords(Flags, XCoordsBin, 0, 2, []), 1406 {YCs, _} = parse_coords(Flags, YCoordsBin, 0, 4, []), 1407 N = length(Flags), 1408 setup_vertices(Flags, XCs, YCs, GlyphDesc); 1409 NumberOfContours =:= -1 -> 1410 %% Several Glyphs (Compound shapes) 1411 get_glyph_shapes(GlyphDesc, TTF, []); 1412 NumberOfContours < -1 -> 1413 throw({error, bad_ttf, ?__(1, "Unsupported TTF format")}); 1414 NumberOfContours =:= 0 -> 1415 [] 1416 end. 1417 1418parse_flags(N, 0, <<Flag:8, Rest/binary>>, Flags) 1419 when N > 0 -> 1420 case (Flag band 8) > 1 of 1421 false -> 1422 parse_flags(N-1, 0, Rest, [Flag|Flags]); 1423 true -> 1424 <<Repeat:8, Next/binary>> = Rest, 1425 parse_flags(N-1, Repeat, Next, [Flag|Flags]) 1426 end; 1427parse_flags(N, R, Rest, Flags = [Prev|_]) 1428 when N > 0 -> 1429 parse_flags(N-1, R-1, Rest, [Prev|Flags]); 1430parse_flags(0, 0, Rest, Flags) -> {lists:reverse(Flags), Rest}. 1431 1432%% repeat(0, _, Flags) -> Flags; 1433%% repeat(N, Flag, Flags) -> repeat(N-1, Flag, [Flag|Flags]). 1434 1435parse_coords([Flag|Flags], <<DX:8, Coords/binary>>, X0, Mask, Xs) 1436 when (Flag band Mask) > 1, (Flag band (Mask*8)) > 1 -> 1437 X = X0+DX, 1438 parse_coords(Flags, Coords, X, Mask, [X|Xs]); 1439parse_coords([Flag|Flags], <<DX:8, Coords/binary>>, X0, Mask, Xs) 1440 when (Flag band Mask) > 1 -> 1441 X = X0-DX, 1442 parse_coords(Flags, Coords, X, Mask, [X|Xs]); 1443parse_coords([Flag|Flags], Coords, X, Mask, Xs) 1444 when (Flag band (Mask*8)) > 1 -> 1445 parse_coords(Flags, Coords, X, Mask, [X|Xs]); 1446parse_coords([_|Flags], <<DX:?S16, Coords/binary>>, X0, Mask, Xs) -> 1447 X = X0 + DX, 1448 parse_coords(Flags, Coords, X, Mask, [X|Xs]); 1449parse_coords([], Rest, _, _, Xs) -> 1450 {lists:reverse(Xs), Rest}. 1451 1452setup_vertices(Flags, XCs, YCs, GlyphDesc) -> 1453 setup_vertices(Flags, XCs, YCs, GlyphDesc, 0, -1, {0,0}, false,false, []). 1454 1455setup_vertices([Flag|Fs0], [X|XCs0], [Y|YCs0], GD, StartC, Index, 1456 S0, WasOff, StartOff0, Vs0) 1457 when StartC < 2 -> 1458 Vs1 = case StartC of 1459 0 -> Vs0; %% First 1460 1 -> close_shape(Vs0, S0, WasOff, StartOff0) 1461 end, 1462 %% Start new one 1463 <<Next0:?U16, NextGD/binary>> = GD, 1464 Next = Next0-Index, 1465 case (Flag band 1) =:= 0 of 1466 true when Fs0 =/= [] -> 1467 StartOff = {X,Y}, %% Save for warparound 1468 [FN|Fs1] = Fs0, 1469 [XN|Xcs1] = XCs0, 1470 [YN|Ycs1] = YCs0, 1471 {S,Skip,Fs,XCs,YCs} = 1472 case ((FN band 1) =:= 0) of 1473 true -> %% Next is also off 1474 {{(X+XN) div 2, (Y+YN) div 2},0, 1475 Fs0, XCs0, YCs0}; 1476 false -> 1477 {{XN, YN},1,Fs1,Xcs1,Ycs1} 1478 end, 1479 %%io:format("SOff ~p ~p ~p~n",[(Flag band 1) =:= 0, S, Next]), 1480 Vs = set_vertex(Vs1, move, S, {0,0}), 1481 setup_vertices(Fs,XCs,YCs,NextGD,Next-Skip,Next0,S,false,StartOff,Vs); 1482 _ -> 1483 S = {X,Y}, 1484 %%io:format("Start ~p ~p ~p~n",[(Flag band 1) =:= 0, S, Next]), 1485 Vs = set_vertex(Vs1, move, S, {0,0}), 1486 setup_vertices(Fs0,XCs0,YCs0,NextGD,Next,Next0,S,false,false,Vs) 1487 end; 1488setup_vertices([Flag|Fs], [X|XCs], [Y|YCs], GD, Next,Index,S,WasOff,StartOff,Vs0) -> 1489 %%io:format("~p ~p~n",[(Flag band 1) =:= 0, WasOff /= false]), 1490 case {(Flag band 1) =:= 0, WasOff} of 1491 {true, {Cx,Cy}} -> 1492 %% two off-curve control points in a row means interpolate an on-curve midpoint 1493 Int = {(X+Cx) div 2, (Y+Cy) div 2}, 1494 Vs = set_vertex(Vs0, curve, Int, WasOff), 1495 setup_vertices(Fs,XCs,YCs, GD, Next-1,Index,S,{X,Y}, StartOff, Vs); 1496 {true, false} -> 1497 setup_vertices(Fs,XCs,YCs, GD, Next-1,Index,S,{X,Y}, StartOff, Vs0); 1498 {false,false} -> 1499 Vs = set_vertex(Vs0, line, {X,Y}, {0,0}), 1500 setup_vertices(Fs,XCs,YCs, GD, Next-1,Index,S,false, StartOff, Vs); 1501 {false,C} -> 1502 Vs = set_vertex(Vs0, curve, {X,Y}, C), 1503 setup_vertices(Fs,XCs,YCs, GD, Next-1,Index,S,false, StartOff, Vs) 1504 end; 1505setup_vertices([], [], [], _, _Next, _, S, WasOff, StartOff, Vs) -> 1506 lists:reverse(close_shape(Vs, S, WasOff, StartOff)). 1507 1508close_shape(Vs0, S={SX,SY}, C={CX,CY}, SC={_SCX,_SCY}) -> 1509 Vs1 = set_vertex(Vs0, curve, {(SX+CX) div 2, (SY+CY) div 2}, C), 1510 set_vertex(Vs1, curve, S, SC); 1511close_shape(Vs, S, false, SC={_SCX,_SCY}) -> 1512 set_vertex(Vs, curve, S, SC); 1513close_shape(Vs, S, C={_CX,_CY}, false) -> 1514 set_vertex(Vs, curve, S, C); 1515close_shape(Vs, S, false, false) -> 1516 set_vertex(Vs, line, S, {0,0}). 1517 1518set_vertex(Vs, Mode, Pos, C) -> 1519 %% io:format(" ~p ~p ~p~n",[Mode, Pos, C]), 1520 [#vertex{type=Mode, pos=Pos, c=C}|Vs]. 1521set_vertex(Vs, Mode, Pos, C, C1) -> 1522 %% io:format(" ~p ~p ~p ~p~n", [Mode, Pos, C, C1]), 1523 [#vertex{type=Mode, pos=Pos, c=C, c1=C1}|Vs]. 1524 1525get_glyph_shapes(<<Flags:?S16, GidX:?S16, GlyphDesc0/binary>>, Font, Vs0) -> 1526 {ScaleInfo,GlyphDesc} = find_trans_scales(Flags, GlyphDesc0), 1527 Vs1 = get_glyph_shape(Font, GidX), 1528 Vs = scale_vertices(Vs1, ScaleInfo, Vs0), 1529 case (Flags band (1 bsl 5)) > 1 of 1530 true -> %% More Components 1531 get_glyph_shapes(GlyphDesc, Font, Vs); 1532 false -> 1533 lists:reverse(Vs) 1534 end. 1535 1536find_trans_scales(Flags, 1537 <<Mtx4:?S16, Mtx5:?S16, GlyphDesc/binary>>) 1538 when (Flags band 3) > 2 -> 1539 find_trans_scales(Flags, Mtx4, Mtx5, GlyphDesc); 1540find_trans_scales(Flags, <<Mtx4:8, Mtx5:8, GlyphDesc/binary>>) 1541 when (Flags band 2) > 1 -> 1542 find_trans_scales(Flags, Mtx4, Mtx5, GlyphDesc). 1543%% @TODO handle matching point 1544%%find_trans_scales(Flags, GlyphDesc0) -> 1545 1546find_trans_scales(Flags, Mtx4, Mtx5, <<Mtx0:?S16, GlyphDesc/binary>>) 1547 when (Flags band (1 bsl 3)) > 1 -> 1548 %% We have a scale 1549 S = 1 / 16384, 1550 {calc_trans_scales(Mtx0*S, 0, 0, Mtx0*S, Mtx4, Mtx5),GlyphDesc}; 1551find_trans_scales(Flags, Mtx4, Mtx5, <<Mtx0:?S16, Mtx3:?S16, GlyphDesc/binary>>) 1552 when (Flags band (1 bsl 6)) > 1 -> 1553 %% We have a X and Y scale 1554 S = 1 / 16384, 1555 {calc_trans_scales(Mtx0*S, 0, 0, Mtx3*S, Mtx4, Mtx5), GlyphDesc}; 1556find_trans_scales(Flags, Mtx4, Mtx5, 1557 <<Mtx0:?S16, Mtx1:?S16, 1558 Mtx2:?S16, Mtx3:?S16, GlyphDesc/binary>>) 1559 when (Flags band (1 bsl 7)) > 1 -> 1560 %% We have a two by two 1561 S = 1 / 16384, 1562 {calc_trans_scales(Mtx0*S, Mtx1*S, Mtx2*S, Mtx3*S, Mtx4, Mtx5), GlyphDesc}; 1563find_trans_scales(_, Mtx4, Mtx5, GlyphDesc) -> 1564 {calc_trans_scales(1.0, 0.0, 0.0, 1.0, Mtx4, Mtx5), GlyphDesc}. 1565 1566calc_trans_scales(Mtx0, Mtx1, Mtx2, Mtx3, Mtx4, Mtx5) -> 1567 {math:sqrt(square(Mtx0)+square(Mtx1)), 1568 math:sqrt(square(Mtx2)+square(Mtx3)), Mtx0, Mtx1, Mtx2, Mtx3, Mtx4, Mtx5}. 1569 1570scale_vertices([#vertex{pos={X,Y}, c={CX,CY}, type=Type}|Vs], 1571 SI={M,N, Mtx0, Mtx1, Mtx2, Mtx3, Mtx4, Mtx5}, Acc) -> 1572 V = #vertex{type=Type, 1573 pos = {round(M*(Mtx0*X+Mtx2*Y+Mtx4)), 1574 round(N*(Mtx1*X+Mtx3*Y+Mtx5))}, 1575 c = {round(M*(Mtx0*CX+Mtx2*CY+Mtx4)), 1576 round(N*(Mtx1*CX+Mtx3*CY+Mtx5))}}, 1577 scale_vertices(Vs, SI, [V|Acc]); 1578scale_vertices([], _, Acc) -> Acc. 1579 1580square(X) -> X*X. 1581 1582%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1583%% CFF stuff 1584 1585pp_cff(undefined, _Bin, _File) -> 1586 undefined; 1587pp_cff(CffIdx, Bin0, _DbgFontFile) -> 1588 <<_:CffIdx/binary,Cff/binary>> = Bin0, 1589 <<1, _MinorVsn, HeaderSz, ?SKIP>> = Cff, 1590 <<_:HeaderSz/binary, Cont0/binary>> = Cff, 1591 {_NameIdx, Cont1} = cff_index(Cont0), 1592 {TopDictIdx, Cont2} = cff_index(Cont1), 1593 TopDict = get_cff_index(0, TopDictIdx), %% OpenType only supports one 1594 %?DBG("Strings: ~.16b~n",[byte_size(Cff) - byte_size(Cont2)]), 1595 {_StringIdx, Cont3} = cff_index(Cont2), 1596 %?DBG("Global Subrs: ~.16b~n",[byte_size(Cff) - byte_size(Cont3)]), 1597 {Gsubrs, _Cont4} = cff_index(Cont3), 1598 CharStringsOff = get_cff_dict(17,TopDict), 1599 CsType = get_cff_dict({12,6},TopDict), 1600 FdArrayOff = get_cff_dict({12,36},TopDict), 1601 FdSelectOff = get_cff_dict({12,37},TopDict), 1602 if CsType =:= 2; CsType =:= [], CharStringsOff /= [] -> ok; 1603 true -> throw({error, bad_cff_cstype, ?__(1, "Not supported OTF (cff) format")}) 1604 end, 1605 1606 Fd = case FdArrayOff of 1607 [] -> undefined; 1608 _ when FdSelectOff =/= [] -> %% Looks like a CID font? 1609 <<_:FdArrayOff/binary, FdArrBin/binary>> = Cff, 1610 {FontDicts, _} = cff_index(FdArrBin), 1611 %% ?DBG("~s: ~p ~p ~p~n",[_DbgFontFile, FdArrayOff, FdSelectOff, size(Cff)]), 1612 <<_:FdSelectOff/binary, FdSelectBin/binary>> = Cff, 1613 {FontDicts, FdSelectBin}; 1614 _ -> 1615 throw({error, no_fd_select, ?__(1, "Not supported OTF (cff) format")}) 1616 end, 1617 Subrs = cff_get_subrs(TopDict, Cff), 1618 %% ?DBG("ChStr ~p type ~p FdA ~p FdS ~p ~p~n", 1619 %% [CharStringsOff, CsType, FdArrayOff, FdSelectOff, size(Cff)]), 1620 <<_:CharStringsOff/binary, CharSBin/binary>> = Cff, 1621 {CharStrings, _Rest} = cff_index(CharSBin), 1622 #{topDict => TopDict, 1623 gsubrs => Gsubrs, 1624 subrs => Subrs, 1625 charStrings => CharStrings, 1626 fdSel => Fd, 1627 cff => Cff 1628 }. 1629 1630cff_get_subrs(Dict, Bin) -> 1631 case get_cff_dict(18, Dict) of 1632 [Size, Offset] when Size /= 0, Offset /= 0 -> 1633 %% ?DBG("~p ~p ~.16b~n",[Size, Offset, Offset]), 1634 <<_:Offset/binary, PrivDict:Size/binary, _Rest/binary>> = Bin, 1635 case get_cff_dict(19, PrivDict) of 1636 [] -> undefined; 1637 SubrsOffs0 -> 1638 SubrsOffs = SubrsOffs0 + Offset, 1639 <<_:SubrsOffs/binary, Subrs/binary>> = Bin, 1640 {Index, _} = cff_index(Subrs), 1641 Index 1642 end; 1643 _ -> 1644 undefined 1645 end. 1646 1647cff_index(<<0:16, Rest0/binary>>) -> 1648 {undefined, Rest0}; 1649cff_index(<<Count:16, OffSz:8, Rest0/binary>>) when 1 =< OffSz, OffSz =< 4 -> 1650 TabSz = OffSz*(Count+1), Bits = OffSz*8, 1651 %% io:format("~p ~p ~p ~p~n", [Count, OffSz, TabSz, Bits]), 1652 <<Offsets0:TabSz/binary, Rest1/binary>> = Rest0, 1653 Offsets = [OS-1 || << OS:Bits >> <= Offsets0], 1654 Last = lists:last(Offsets), 1655 %% io:format("~p ~p ~p ~p ~p~n", [Count, OffSz, TabSz, Bits, Last]), 1656 <<_:Last/binary, Rest/binary>> = Rest1, 1657 {{array:from_list(Offsets), Rest1}, Rest}. 1658 1659get_cff_index(Idx, {Offsets, Bin}) -> 1660 Offset = array:get(Idx, Offsets), 1661 Sz = array:get(Idx+1, Offsets) - Offset, 1662 <<_:Offset/binary, Data:Sz/binary, ?SKIP>> = Bin, 1663 Data. 1664 1665get_cff_index_count({Offsets, _Bin}) -> 1666 array:size(Offsets). 1667 1668get_cff_dict(Key, Bin) -> 1669 get_ccf_dict(Bin, [], Key). 1670 1671get_ccf_dict(<<Data, ?SKIP>>=Bin, Vals, Key) 1672 when Data >= 28 -> 1673 {Val, Rest} = ccf_dict_operand(Bin), 1674 get_ccf_dict(Rest, [Val|Vals], Key); 1675get_ccf_dict(<<Key, ?SKIP>>, Val, Key) -> 1676 dict_val(Val); 1677get_ccf_dict(<<12, Op, Rest/binary>>, Val, Key) -> 1678 case Key of 1679 {12,Op} -> dict_val(Val); 1680 _ -> get_ccf_dict(Rest, [], Key) 1681 end; 1682get_ccf_dict(<<_Key, Rest/binary>>, _Val, Key) -> 1683 %% io:format("Not found ~p: ~p~n",[_Key,_Val]), 1684 get_ccf_dict(Rest, [], Key); 1685get_ccf_dict(<<>>, [], _) -> 1686 []. 1687 1688dict_val([{float, _Bin}]) -> 1689 throw({nyi, cff_float}); 1690dict_val([Val]) -> 1691 Val; 1692dict_val(Vals) when is_list(Vals) -> 1693 lists:reverse(Vals). 1694 1695ccf_dict_operand(<<28, Val:?S16, Rest/binary>>) -> 1696 {Val,Rest}; 1697ccf_dict_operand(<<29, Val:?S32, Rest/binary>>) -> 1698 {Val,Rest}; 1699ccf_dict_operand(<<30, Bin/binary>>) -> 1700 Rest = ccf_dict_skip_float(Bin), 1701 %% Sz = byte_size(Bin) - byte_size(Rest), 1702 %% <<FloatBin:Sz, ?SKIP>> = Rest, 1703 {{float, aFloatBin}, Rest}; 1704ccf_dict_operand(<<B, Rest/binary>>) 1705 when 31 < B, B < 247 -> 1706 {B-139, Rest}; 1707ccf_dict_operand(<<B0, B1, Rest/binary>>) 1708 when 31 < B0, B0 < 255 -> 1709 case B0 < 251 of 1710 true -> {(B0-247)*256+B1+108, Rest}; 1711 false -> {-(B0-251)*256-B1-108, Rest} 1712 end. 1713 1714ccf_dict_skip_float(<<16#F:4, _:4, Rest/binary>>) -> Rest; 1715ccf_dict_skip_float(<<_:4, 16#F:4, Rest/binary>>) -> Rest; 1716ccf_dict_skip_float(<<_, Rest/binary>>) -> 1717 ccf_dict_skip_float(Rest). 1718 1719get_glyph_subrs(Glyph, {FontDicts, <<Fmt, FdSelBin/binary>>}, Cff) -> 1720 FdSel = case Fmt of 1721 0 -> %% Untested !!! 1722 <<_:Glyph/binary, FdSelByte, ?SKIP>> = FdSelBin, 1723 FdSelByte; 1724 3 -> 1725 <<NRanges:?S16, Start:?S16, Cont0/binary>> = FdSelBin, 1726 get_glyph_fd(NRanges, Start, Glyph, Cont0) 1727 end, 1728 %% ?DBG("Subr Format ~p sel: ~p index: ~p~n",[Fmt, FdSel, get_cff_index(FdSel, FontDicts)]), 1729 cff_get_subrs(get_cff_index(FdSel, FontDicts), Cff). 1730 1731get_glyph_fd(N, Start, Glyph, Bin) when N > 0 -> 1732 <<V:8, End:?U16, Cont/binary>> = Bin, 1733 case Start =< Glyph andalso Glyph < End of 1734 true -> V; 1735 false -> get_glyph_fd(N-1, End, Glyph, Cont) 1736 end; 1737get_glyph_fd(_, _, _, _) -> 1738 throw({error, fd_not_found, "Not supported"}). 1739 1740get_glyph_shape_tt2(#ttf_info{cff=CFF} = _TTF, Glyph) -> 1741 run_charstring(CFF, Glyph). 1742 1743run_charstring(#{charStrings := CharSs}=Cff, Glyph) -> 1744 Ops = get_cff_index(Glyph, CharSs), 1745 State = Cff#{ %% Add the following temporary variables 1746 in_header => true, 1747 maskbits => 0, 1748 has_subrs => false, 1749 glyph => Glyph 1750 }, 1751 %% ?DBG("Glyph: ~w ~P~n",[Glyph, Ops, 40]), 1752 {return, {_,_,All}} = run_chars(Ops, [], State, csctx_new()), 1753 lists:reverse(All). 1754 1755run_chars(Bin, Stack, State, Acc) -> 1756 %% ?DBG("~.16b (~w) ~w~n", [binary:first(Bin), length(Stack), Stack]), 1757 do_run_chars(Bin, Stack, State, Acc). 1758 1759do_run_chars(<<16#13, Rest0/binary>>, Stack, #{maskbits:=MB0, in_header:=InH}=State, Acc)-> 1760 %% Hintmask NYI 1761 MB = case InH of 1762 true -> (length(Stack) div 2) + MB0; 1763 false -> MB0 1764 end, 1765 Skip = (MB + 7) div 8, 1766 <<_:Skip/binary, Rest/binary>> = Rest0, 1767 run_chars(Rest, [], State#{maskbits:=MB,in_header:=false}, Acc); 1768do_run_chars(<<16#14, Rest0/binary>>, Stack, #{maskbits:=MB0, in_header:=InH}=State, Acc) -> 1769 %% CNTRMASK 1770 MB = case InH of 1771 true -> (length(Stack) div 2) + MB0; 1772 false -> MB0 1773 end, 1774 Skip = (MB + 7) div 8, 1775 <<_:Skip/binary, Rest/binary>> = Rest0, 1776 run_chars(Rest, [], State#{maskbits:=MB,in_header=>false}, Acc); 1777do_run_chars(<<Stem, Rest/binary>>, Stack, #{maskbits:=MB0}=State, Acc) 1778 when Stem =:= 16#01; %% hstem 1779 Stem =:= 16#03; %% vstem 1780 Stem =:= 16#12; %% hstemhm 1781 Stem =:= 16#17 -> %% vstemhm 1782 MB = (length(Stack) div 2) + MB0, 1783 run_chars(Rest, [], State#{maskbits:=MB}, Acc); 1784 1785do_run_chars(<<16#15, Rest/binary>>, [S2,S1|_], State, Acc0) -> 1786 %% rmoveto 1787 Acc = csctx_rmove_to(S1,S2,Acc0), 1788 run_chars(Rest, [], State#{in_header:=false}, Acc); 1789do_run_chars(<<16#04,Rest/binary>>, [S1|_], State, Acc0) -> 1790 %% vmoveto 1791 Acc = csctx_rmove_to(0, S1, Acc0), 1792 run_chars(Rest, [], State#{in_header:=false}, Acc); 1793do_run_chars(<<16#16,Rest/binary>>, [S1|_], State, Acc0) -> 1794 %% hmoveto 1795 Acc = csctx_rmove_to(S1, 0, Acc0), 1796 run_chars(Rest, [], State#{in_header:=false}, Acc); 1797 1798do_run_chars(<<16#05,Rest/binary>>, Stack, State, Acc0) -> 1799 Acc = rlineto(reverse(Stack), Acc0), 1800 run_chars(Rest, [], State#{in_header:=false}, Acc); 1801 1802do_run_chars(<<16#06,Rest/binary>>, Stack, State, Acc0) -> 1803 Acc = hlineto(reverse(Stack), Acc0), 1804 run_chars(Rest, [], State#{in_header:=false}, Acc); 1805do_run_chars(<<16#07,Rest/binary>>, Stack, State, Acc0) -> 1806 Acc = vlineto(reverse(Stack), Acc0), 1807 run_chars(Rest, [], State#{in_header:=false}, Acc); 1808 1809do_run_chars(<<16#1E,Rest/binary>>, Stack, State, Acc0) -> 1810 Acc = vhcurveto(reverse(Stack), Acc0), 1811 run_chars(Rest, [], State, Acc); 1812do_run_chars(<<16#1F,Rest/binary>>, Stack, State, Acc0) -> 1813 Acc = hvcurveto(reverse(Stack), Acc0), 1814 run_chars(Rest, [], State, Acc); 1815do_run_chars(<<16#08,Rest/binary>>, Stack, State, Acc0) -> 1816 Acc = rrcurveto(reverse(Stack), Acc0), 1817 run_chars(Rest, [], State, Acc); 1818 1819do_run_chars(<<16#18,Rest/binary>>, Stack, State, Acc0) -> 1820 Acc = rrcurveline(reverse(Stack), Acc0), 1821 run_chars(Rest, [], State, Acc); 1822do_run_chars(<<16#19,Rest/binary>>, Stack, State, Acc0) -> 1823 Acc = rrlinecurve(reverse(Stack), Acc0), 1824 run_chars(Rest, [], State, Acc); 1825 1826do_run_chars(<<16#1A,Rest/binary>>, Stack0, State, Acc0) -> 1827 [F|Stack1] = Stack = reverse(Stack0), 1828 Acc = case length(Stack) rem 2 of 1829 1 -> vvcurveto(Stack1, F, Acc0); 1830 0 -> vvcurveto(Stack, 0.0, Acc0) 1831 end, 1832 run_chars(Rest, [], State, Acc); 1833do_run_chars(<<16#1B,Rest/binary>>, Stack0, State, Acc0) -> 1834 [F|Stack1] = Stack = reverse(Stack0), 1835 Acc = case length(Stack) rem 2 of 1836 1 -> hhcurveto(Stack1, F, Acc0); 1837 0 -> hhcurveto(Stack, 0.0, Acc0) 1838 end, 1839 run_chars(Rest, [], State, Acc); 1840 1841do_run_chars(<<16#0A,Rest/binary>>, [V|Stack0], State0, Acc0) -> 1842 {State1,Subrs} = 1843 case maps:get(has_subrs, State0) orelse maps:get(fdSel,State0, undefined) of 1844 true -> {State0, maps:get(subrs, State0)}; 1845 undefined -> {State0, maps:get(subrs, State0)}; 1846 FdSel -> 1847 #{cff := Cff, glyph := Glyph} = State0, 1848 GlSubrs = get_glyph_subrs(Glyph, FdSel, Cff), 1849 {State0#{has_subrs := true, subrs := GlSubrs}, GlSubrs} 1850 end, 1851 %%?DBG("Recurse ...~p~n",[Stack0]), 1852 case callsubr(V, Subrs, Stack0, State1, Acc0) of 1853 {subrr, Stack, State, Acc} -> 1854 %%?DBG("...done ~p~n",[Stack]), 1855 run_chars(Rest, Stack, State, Acc); 1856 {return, _} = Acc -> 1857 %%?DBG("...done return~n",[]), 1858 <<>> = Rest, %% Assert 1859 Acc 1860 end; 1861do_run_chars(<<16#1D,Rest/binary>>, [V|Stack0], #{gsubrs := GSubrs} = State0, Acc0) -> 1862 %%?DBG("Recurse ...~p~n",[Stack0]), 1863 case callsubr(V, GSubrs, Stack0, State0, Acc0) of 1864 {subrr, Stack, State, Acc} -> 1865 %%?DBG("...done ~p~n",[Stack]), 1866 run_chars(Rest, Stack, State, Acc); 1867 {return, _} = Acc -> 1868 %%?DBG("...done return~n",[]), 1869 <<>> = Rest, %% Assert 1870 Acc 1871 end; 1872 1873do_run_chars(<<16#0B,Rest/binary>>, Stack, State, Acc) -> 1874 %% Return (subr) 1875 <<>> = Rest, 1876 {subrr, Stack, State, Acc}; 1877do_run_chars(<<16#0E,Rest/binary>>, _, _State, Acc) -> 1878 %% endchar 1879 <<>> = Rest, 1880 {return, csctx_close_shape(Acc)}; 1881 1882do_run_chars(<<16#0C,B1,Rest/binary>>, Stack, State, Acc0) -> 1883 %% Two-byte Escape-seq 1884 Acc = run_char(B1,Stack,Acc0), 1885 run_chars(Rest, [], State, Acc); 1886do_run_chars(<<16#FF, Int:32, Rest/binary>>, Stack, State, Acc) -> 1887 run_chars(Rest, [Int/16#10000|Stack], State, Acc); 1888do_run_chars(Bin, Stack, State, Acc) -> 1889 try ccf_dict_operand(Bin) of 1890 {Val, Rest} -> 1891 run_chars(Rest, [Val|Stack], State, Acc) 1892 catch _:_ -> 1893 <<Byte, _/binary>> = Bin, 1894 io:format("Bad op: 16#~.16b ~p~n ~P~n ~P~nin ~P~n", 1895 [Byte, Stack, State, 10, Acc, 20, Bin, 40]), 1896 throw({error, parse_error}) 1897 end. 1898 1899run_char(16#22, [Dx6,Dx5,Dx4,Dx3,Dy2,Dx2,Dx1], Acc0) -> 1900 %% hflex 1901 Acc = csctx_rccurve_to(Dx1,0,Dx2,Dy2,Dx3,0,Acc0), 1902 csctx_rccurve_to(Dx4,0,Dx5,-Dy2,Dx6,0,Acc); 1903run_char(16#23, [_Fd, Dy6,Dx6,Dy5,Dx5,Dy4,Dx4,Dy3,Dx3,Dy2,Dx2,Dy1,Dx1], Acc0) -> 1904 %% flex 1905 Acc = csctx_rccurve_to(Dx1,Dy1,Dx2,Dy2,Dx3,Dy3,Acc0), 1906 csctx_rccurve_to(Dx4,Dy4,Dx5,Dy5,Dx6,Dy6,Acc); 1907run_char(16#24, [Dx6,Dy5,Dx5,Dx4,Dx3,Dy2,Dx2,Dy1,Dx1], Acc0) -> 1908 %% hflex1 1909 Acc = csctx_rccurve_to(Dx1,Dy1,Dx2,Dy2,Dx3,0,Acc0), 1910 csctx_rccurve_to(Dx4,0,Dx5,Dy5,Dx6,-(Dy1+Dy2+Dy5),Acc); 1911run_char(16#25, [D6,Dy5,Dx5,Dy4,Dx4,Dy3,Dx3,Dy2,Dx2,Dy1,Dx1], Acc0) -> 1912 %% flex1 1913 Dx = Dx1+Dx2+Dx3+Dx4+Dx5, 1914 Dy = Dy1+Dy2+Dy3+Dy4+Dy5, 1915 {Dx6,Dy6} = case abs(Dx) > abs(Dy) of 1916 true -> {D6, -Dy}; 1917 false -> {-Dx, D6} 1918 end, 1919 Acc = csctx_rccurve_to(Dx1,Dy1,Dx2,Dy2,Dx3,Dy3,Acc0), 1920 csctx_rccurve_to(Dx4,Dy4,Dx5,Dy5,Dx6,Dy6,Acc). 1921 1922callsubr(N0, Subrs, Stack, State, Acc) -> 1923 Count = get_cff_index_count(Subrs), 1924 Bias = if Count >= 33900 -> 32768; 1925 Count >= 1240 -> 1131; 1926 true -> 107 1927 end, 1928 N = N0+Bias, 1929 ((N < 0) orelse (N >= Count)) andalso 1930 throw({error, internal_error, "A bug is found"}), 1931 Bin = get_cff_index(N, Subrs), 1932 %% ?DBG("sub ~W~n",[Bin, 40]), 1933 run_chars(Bin, Stack, State, Acc). 1934 1935rlineto([S0,S1|St], Acc) -> 1936 rlineto(St, csctx_rline_to(S0,S1,Acc)); 1937rlineto([], Acc) -> Acc. 1938 1939%% Note: hlineto/vlineto alternate horizontal and vertical 1940%% starting from a different place. 1941hlineto([S0|St], Acc0) -> 1942 vlineto(St, csctx_rline_to(S0, 0, Acc0)); 1943hlineto([], Acc) -> Acc. 1944 1945vlineto([S0|St], Acc0) -> 1946 hlineto(St, csctx_rline_to(0, S0, Acc0)); 1947vlineto([], Acc) -> Acc. 1948 1949%% Note: vhcurveto/hvcurveto alternate horizontal and vertical 1950%% starting from a different place. 1951vhcurveto([S0,S1,S2,S3|St], Acc0) -> 1952 S4 = case St of 1953 [Last]-> Last; 1954 _ -> 0.0 1955 end, 1956 Acc = csctx_rccurve_to(0, S0, S1, S2, S3, S4, Acc0), 1957 hvcurveto(St, Acc); 1958vhcurveto(_, Acc) -> Acc. 1959 1960hvcurveto([S0,S1,S2,S3|St], Acc0) -> 1961 S4 = case St of 1962 [Last] -> Last; 1963 _ -> 0.0 1964 end, 1965 Acc = csctx_rccurve_to(S0, 0, S1, S2, S4, S3, Acc0), 1966 vhcurveto(St, Acc); 1967hvcurveto(_, Acc) -> Acc. 1968 1969rrcurveto([S0,S1,S2,S3,S4,S5|St], Acc0) -> 1970 Acc = csctx_rccurve_to(S0,S1,S2,S3,S4,S5,Acc0), 1971 rrcurveto(St,Acc); 1972rrcurveto(_, Acc) -> Acc. 1973 1974rrcurveline([S0,S1,S2,S3,S4,S5|St], Acc0) -> 1975 Acc = csctx_rccurve_to(S0,S1,S2,S3,S4,S5,Acc0), 1976 rrcurveline(St, Acc); 1977rrcurveline([S0,S1], Acc0) -> 1978 csctx_rline_to(S0, S1, Acc0). 1979 1980rrlinecurve([S0,S1|St], Acc0) 1981 when length(St) >= 6 -> 1982 Acc = csctx_rline_to(S0, S1, Acc0), 1983 rrlinecurve(St, Acc); 1984rrlinecurve([S0,S1,S2,S3,S4,S5], Acc0) -> 1985 csctx_rccurve_to(S0,S1,S2,S3,S4,S5,Acc0). 1986 1987vvcurveto([S0,S1,S2,S3|St], F, Acc0) -> 1988 Acc = csctx_rccurve_to(F,S0,S1,S2,0.0,S3,Acc0), 1989 vvcurveto(St, 0.0, Acc); 1990vvcurveto([], _, Acc) -> Acc. 1991 1992hhcurveto([S0,S1,S2,S3|St], F, Acc0) -> 1993 Acc = csctx_rccurve_to(S0,F,S1,S2,S3,0.0,Acc0), 1994 hhcurveto(St, 0.0, Acc); 1995hhcurveto([], _, Acc) -> Acc. 1996 1997csctx_new() -> 1998 {{0.0,0.0},{0.0,0.0},[]}. 1999 2000csctx_close_shape({{Fx,Fy}=First, {X,Y}=XY, Shapes} = Acc) -> 2001 case Fx /= X orelse Fy /= Y of 2002 true -> 2003 {First, XY, set_vertex(Shapes, line, First, {0,0})}; 2004 false -> 2005 Acc 2006 end. 2007 2008csctx_rmove_to(Dx,Dy,Acc0) -> 2009 {_First, {X,Y}, Shs} = csctx_close_shape(Acc0), 2010 XY = {X+Dx,Y+Dy}, 2011 {XY, XY, set_vertex(Shs, move, XY, {0,0})}. 2012 2013csctx_rline_to(Dx,Dy,{First,{X,Y},Shs}) -> 2014 XY = {X+Dx,Y+Dy}, 2015 {First, XY, set_vertex(Shs, line, XY, {0,0})}. 2016 2017csctx_rccurve_to(Dx1,Dy1,Dx2,Dy2,Dx3,Dy3,{First,{X,Y},Shs}) -> 2018 Cx1 = X + Dx1, 2019 Cy1 = Y + Dy1, 2020 Cx2 = Cx1 + Dx2, 2021 Cy2 = Cy1 + Dy2, 2022 XY = {Cx2 + Dx3,Cy2 + Dy3}, 2023 {First, XY, set_vertex(Shs, cubic, XY, {Cx1,Cy1}, {Cx2,Cy2})}. 2024 2025 2026