| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823 |
- // Copyright (C) 2004-2025 Artifex Software, Inc.
- //
- // This file is part of MuPDF.
- //
- // MuPDF is free software: you can redistribute it and/or modify it under the
- // terms of the GNU Affero General Public License as published by the Free
- // Software Foundation, either version 3 of the License, or (at your option)
- // any later version.
- //
- // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
- // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
- // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
- // details.
- //
- // You should have received a copy of the GNU Affero General Public License
- // along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
- //
- // Alternative licensing terms are available from the licensor.
- // For commercial licensing, see <https://www.artifex.com/> or contact
- // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
- // CA 94129, USA, for further information.
- #include "mupdf/fitz.h"
- #include "svg-imp.h"
- #include <string.h>
- #include <math.h>
- /* default page size */
- #define DEF_WIDTH 612
- #define DEF_HEIGHT 792
- #define DEF_FONTSIZE 12
- #define MAX_USE_DEPTH 100
- typedef struct svg_state
- {
- fz_matrix transform;
- fz_stroke_state *stroke;
- int use_depth;
- float viewport_w, viewport_h;
- float viewbox_w, viewbox_h, viewbox_size;
- float fontsize;
- float opacity;
- int fill_rule;
- int fill_is_set;
- float fill_color[3];
- float fill_opacity;
- int stroke_is_set;
- float stroke_color[3];
- float stroke_opacity;
- const char *font_family;
- int is_bold;
- int is_italic;
- int text_anchor;
- } svg_state;
- static void svg_parse_common(fz_context *ctx, svg_document *doc, fz_xml *node, svg_state *state);
- static void svg_run_element(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *state);
- void svg_begin_state(fz_context *ctx, svg_state *child, const svg_state *parent)
- {
- memcpy(child, parent, sizeof(svg_state));
- child->stroke = fz_clone_stroke_state(ctx, parent->stroke);
- }
- void svg_end_state(fz_context *ctx, svg_state *child)
- {
- fz_drop_stroke_state(ctx, child->stroke);
- }
- static void svg_fill(fz_context *ctx, fz_device *dev, svg_document *doc, fz_path *path, svg_state *state)
- {
- float opacity = state->opacity * state->fill_opacity;
- if (path)
- fz_fill_path(ctx, dev, path, state->fill_rule, state->transform, fz_device_rgb(ctx), state->fill_color, opacity, fz_default_color_params);
- }
- static void svg_stroke(fz_context *ctx, fz_device *dev, svg_document *doc, fz_path *path, svg_state *state)
- {
- float opacity = state->opacity * state->stroke_opacity;
- if (path)
- fz_stroke_path(ctx, dev, path, state->stroke, state->transform, fz_device_rgb(ctx), state->stroke_color, opacity, fz_default_color_params);
- }
- static void svg_draw_path(fz_context *ctx, fz_device *dev, svg_document *doc, fz_path *path, svg_state *state)
- {
- if (state->fill_is_set)
- svg_fill(ctx, dev, doc, path, state);
- if (state->stroke_is_set)
- svg_stroke(ctx, dev, doc, path, state);
- }
- /*
- We use the MAGIC number 0.551915 as a bezier subdivision to approximate
- a quarter circle arc. The reasons for this can be found here:
- http://mechanicalexpressions.com/explore/geometric-modeling/circle-spline-approximation.pdf
- */
- static const float MAGIC_CIRCLE = 0.551915f;
- static void approx_circle(fz_context *ctx, fz_path *path, float cx, float cy, float rx, float ry)
- {
- float mx = rx * MAGIC_CIRCLE;
- float my = ry * MAGIC_CIRCLE;
- fz_moveto(ctx, path, cx, cy+ry);
- fz_curveto(ctx, path, cx + mx, cy + ry, cx + rx, cy + my, cx + rx, cy);
- fz_curveto(ctx, path, cx + rx, cy - my, cx + mx, cy - ry, cx, cy - ry);
- fz_curveto(ctx, path, cx - mx, cy - ry, cx - rx, cy - my, cx - rx, cy);
- fz_curveto(ctx, path, cx - rx, cy + my, cx - mx, cy + ry, cx, cy + ry);
- fz_closepath(ctx, path);
- }
- static void
- svg_run_rect(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
- {
- svg_state local_state;
- char *x_att = fz_xml_att(node, "x");
- char *y_att = fz_xml_att(node, "y");
- char *w_att = fz_xml_att(node, "width");
- char *h_att = fz_xml_att(node, "height");
- char *rx_att = fz_xml_att(node, "rx");
- char *ry_att = fz_xml_att(node, "ry");
- float x = 0;
- float y = 0;
- float w = 0;
- float h = 0;
- float rx = 0;
- float ry = 0;
- fz_path *path = NULL;
- fz_var(path);
- fz_try(ctx)
- {
- svg_begin_state(ctx, &local_state, inherit_state);
- svg_parse_common(ctx, doc, node, &local_state);
- if (x_att) x = svg_parse_length(x_att, local_state.viewbox_w, local_state.fontsize);
- if (y_att) y = svg_parse_length(y_att, local_state.viewbox_h, local_state.fontsize);
- if (w_att) w = svg_parse_length(w_att, local_state.viewbox_w, local_state.fontsize);
- if (h_att) h = svg_parse_length(h_att, local_state.viewbox_h, local_state.fontsize);
- if (rx_att) rx = svg_parse_length(rx_att, local_state.viewbox_w, local_state.fontsize);
- if (ry_att) ry = svg_parse_length(ry_att, local_state.viewbox_h, local_state.fontsize);
- if (rx_att && !ry_att)
- ry = rx;
- if (ry_att && !rx_att)
- rx = ry;
- if (rx > w * 0.5f)
- rx = w * 0.5f;
- if (ry > h * 0.5f)
- ry = h * 0.5f;
- if (w <= 0 || h <= 0)
- return;
- path = fz_new_path(ctx);
- if (rx == 0 || ry == 0)
- {
- fz_moveto(ctx, path, x, y);
- fz_lineto(ctx, path, x + w, y);
- fz_lineto(ctx, path, x + w, y + h);
- fz_lineto(ctx, path, x, y + h);
- }
- else
- {
- float rxs = rx * MAGIC_CIRCLE;
- float rys = rx * MAGIC_CIRCLE;
- fz_moveto(ctx, path, x + w - rx, y);
- fz_curveto(ctx, path, x + w - rxs, y, x + w, y + rys, x + w, y + ry);
- fz_lineto(ctx, path, x + w, y + h - ry);
- fz_curveto(ctx, path, x + w, y + h - rys, x + w - rxs, y + h, x + w - rx, y + h);
- fz_lineto(ctx, path, x + rx, y + h);
- fz_curveto(ctx, path, x + rxs, y + h, x, y + h - rys, x, y + h - rx);
- fz_lineto(ctx, path, x, y + rx);
- fz_curveto(ctx, path, x, y + rxs, x + rxs, y, x + rx, y);
- }
- fz_closepath(ctx, path);
- svg_draw_path(ctx, dev, doc, path, &local_state);
- }
- fz_always(ctx)
- {
- fz_drop_path(ctx, path);
- svg_end_state(ctx, &local_state);
- }
- fz_catch(ctx)
- fz_rethrow(ctx);
- }
- static void
- svg_run_circle(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
- {
- svg_state local_state;
- char *cx_att = fz_xml_att(node, "cx");
- char *cy_att = fz_xml_att(node, "cy");
- char *r_att = fz_xml_att(node, "r");
- float cx = 0;
- float cy = 0;
- float r = 0;
- fz_path *path = NULL;
- fz_var(path);
- fz_try(ctx)
- {
- svg_begin_state(ctx, &local_state, inherit_state);
- svg_parse_common(ctx, doc, node, &local_state);
- if (cx_att) cx = svg_parse_length(cx_att, local_state.viewbox_w, local_state.fontsize);
- if (cy_att) cy = svg_parse_length(cy_att, local_state.viewbox_h, local_state.fontsize);
- if (r_att) r = svg_parse_length(r_att, local_state.viewbox_size, 12);
- if (r > 0)
- {
- path = fz_new_path(ctx);
- approx_circle(ctx, path, cx, cy, r, r);
- svg_draw_path(ctx, dev, doc, path, &local_state);
- }
- }
- fz_always(ctx)
- {
- fz_drop_path(ctx, path);
- svg_end_state(ctx, &local_state);
- }
- fz_catch(ctx)
- fz_rethrow(ctx);
- }
- static void
- svg_run_ellipse(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
- {
- svg_state local_state;
- char *cx_att = fz_xml_att(node, "cx");
- char *cy_att = fz_xml_att(node, "cy");
- char *rx_att = fz_xml_att(node, "rx");
- char *ry_att = fz_xml_att(node, "ry");
- float cx = 0;
- float cy = 0;
- float rx = 0;
- float ry = 0;
- fz_path *path = NULL;
- fz_var(path);
- fz_try(ctx)
- {
- svg_begin_state(ctx, &local_state, inherit_state);
- svg_parse_common(ctx, doc, node, &local_state);
- if (cx_att) cx = svg_parse_length(cx_att, local_state.viewbox_w, local_state.fontsize);
- if (cy_att) cy = svg_parse_length(cy_att, local_state.viewbox_h, local_state.fontsize);
- if (rx_att) rx = svg_parse_length(rx_att, local_state.viewbox_w, local_state.fontsize);
- if (ry_att) ry = svg_parse_length(ry_att, local_state.viewbox_h, local_state.fontsize);
- if (rx > 0 && ry > 0)
- {
- path = fz_new_path(ctx);
- approx_circle(ctx, path, cx, cy, rx, ry);
- svg_draw_path(ctx, dev, doc, path, &local_state);
- }
- }
- fz_always(ctx)
- {
- fz_drop_path(ctx, path);
- svg_end_state(ctx, &local_state);
- }
- fz_catch(ctx)
- fz_rethrow(ctx);
- }
- static void
- svg_run_line(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
- {
- svg_state local_state;
- fz_path *path = NULL;
- char *x1_att = fz_xml_att(node, "x1");
- char *y1_att = fz_xml_att(node, "y1");
- char *x2_att = fz_xml_att(node, "x2");
- char *y2_att = fz_xml_att(node, "y2");
- float x1 = 0;
- float y1 = 0;
- float x2 = 0;
- float y2 = 0;
- fz_var(path);
- fz_try(ctx)
- {
- svg_begin_state(ctx, &local_state, inherit_state);
- svg_parse_common(ctx, doc, node, &local_state);
- if (x1_att) x1 = svg_parse_length(x1_att, local_state.viewbox_w, local_state.fontsize);
- if (y1_att) y1 = svg_parse_length(y1_att, local_state.viewbox_h, local_state.fontsize);
- if (x2_att) x2 = svg_parse_length(x2_att, local_state.viewbox_w, local_state.fontsize);
- if (y2_att) y2 = svg_parse_length(y2_att, local_state.viewbox_h, local_state.fontsize);
- if (local_state.stroke_is_set)
- {
- path = fz_new_path(ctx);
- fz_moveto(ctx, path, x1, y1);
- fz_lineto(ctx, path, x2, y2);
- svg_stroke(ctx, dev, doc, path, &local_state);
- }
- }
- fz_always(ctx)
- {
- fz_drop_path(ctx, path);
- svg_end_state(ctx, &local_state);
- }
- fz_catch(ctx)
- fz_rethrow(ctx);
- }
- static fz_path *
- svg_parse_polygon_imp(fz_context *ctx, svg_document *doc, fz_xml *node, int doclose)
- {
- fz_path *path;
- const char *str = fz_xml_att(node, "points");
- float number;
- float args[2];
- int nargs;
- int isfirst;
- if (!str)
- return NULL;
- isfirst = 1;
- nargs = 0;
- path = fz_new_path(ctx);
- fz_try(ctx)
- {
- while (*str)
- {
- while (svg_is_whitespace_or_comma(*str))
- str ++;
- if (svg_is_digit(*str))
- {
- str = svg_lex_number(&number, str);
- args[nargs++] = number;
- }
- if (nargs == 2)
- {
- if (isfirst)
- {
- fz_moveto(ctx, path, args[0], args[1]);
- isfirst = 0;
- }
- else
- {
- fz_lineto(ctx, path, args[0], args[1]);
- }
- nargs = 0;
- }
- }
- }
- fz_catch(ctx)
- {
- fz_drop_path(ctx, path);
- fz_rethrow(ctx);
- }
- return path;
- }
- static void
- svg_run_polyline(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
- {
- svg_state local_state;
- fz_path *path = NULL;
- fz_var(path);
- fz_try(ctx)
- {
- svg_begin_state(ctx, &local_state, inherit_state);
- svg_parse_common(ctx, doc, node, &local_state);
- if (local_state.stroke_is_set)
- {
- path = svg_parse_polygon_imp(ctx, doc, node, 0);
- svg_stroke(ctx, dev, doc, path, &local_state);
- }
- }
- fz_always(ctx)
- {
- fz_drop_path(ctx, path);
- svg_end_state(ctx, &local_state);
- }
- fz_catch(ctx)
- fz_rethrow(ctx);
- }
- static void
- svg_run_polygon(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
- {
- svg_state local_state;
- fz_path *path = NULL;
- fz_var(path);
- fz_try(ctx)
- {
- svg_begin_state(ctx, &local_state, inherit_state);
- svg_parse_common(ctx, doc, node, &local_state);
- path = svg_parse_polygon_imp(ctx, doc, node, 1);
- svg_draw_path(ctx, dev, doc, path, &local_state);
- }
- fz_always(ctx)
- {
- fz_drop_path(ctx, path);
- svg_end_state(ctx, &local_state);
- }
- fz_catch(ctx)
- fz_rethrow(ctx);
- }
- static void
- svg_add_arc_segment(fz_context *ctx, fz_path *path, fz_matrix mtx, float th0, float th1, int iscw)
- {
- float t, d;
- fz_point p;
- while (th1 < th0)
- th1 += FZ_PI * 2;
- d = FZ_PI / 180; /* 1-degree precision */
- if (iscw)
- {
- for (t = th0 + d; t < th1 - d/2; t += d)
- {
- p = fz_transform_point_xy(cosf(t), sinf(t), mtx);
- fz_lineto(ctx, path, p.x, p.y);
- }
- }
- else
- {
- th0 += FZ_PI * 2;
- for (t = th0 - d; t > th1 + d/2; t -= d)
- {
- p = fz_transform_point_xy(cosf(t), sinf(t), mtx);
- fz_lineto(ctx, path, p.x, p.y);
- }
- }
- }
- static float
- angle_between(const fz_point u, const fz_point v)
- {
- float det = u.x * v.y - u.y * v.x;
- float sign = (det < 0 ? -1 : 1);
- float magu = u.x * u.x + u.y * u.y;
- float magv = v.x * v.x + v.y * v.y;
- float udotv = u.x * v.x + u.y * v.y;
- float t = udotv / (magu * magv);
- /* guard against rounding errors when near |1| (where acos will return NaN) */
- if (t < -1) t = -1;
- if (t > 1) t = 1;
- return sign * acosf(t);
- }
- static void
- svg_add_arc(fz_context *ctx, fz_path *path,
- float size_x, float size_y, float rotation_angle,
- int is_large_arc, int is_clockwise,
- float point_x, float point_y)
- {
- fz_matrix rotmat, revmat;
- fz_matrix mtx;
- fz_point pt;
- float rx, ry;
- float x1, y1, x2, y2;
- float x1t, y1t;
- float cxt, cyt, cx, cy;
- float t1, t2, t3;
- float sign;
- float th1, dth;
- pt = fz_currentpoint(ctx, path);
- x1 = pt.x;
- y1 = pt.y;
- x2 = point_x;
- y2 = point_y;
- rx = size_x;
- ry = size_y;
- if (is_clockwise != is_large_arc)
- sign = 1;
- else
- sign = -1;
- rotmat = fz_rotate(rotation_angle);
- revmat = fz_rotate(-rotation_angle);
- /* http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes */
- /* Conversion from endpoint to center parameterization */
- /* F.6.6.1 -- ensure radii are positive and non-zero */
- rx = fabsf(rx);
- ry = fabsf(ry);
- if (rx < 0.001f || ry < 0.001f || (x1 == x2 && y1 == y2))
- {
- fz_lineto(ctx, path, x2, y2);
- return;
- }
- /* F.6.5.1 */
- pt.x = (x1 - x2) / 2;
- pt.y = (y1 - y2) / 2;
- pt = fz_transform_vector(pt, revmat);
- x1t = pt.x;
- y1t = pt.y;
- /* F.6.6.2 -- ensure radii are large enough */
- t1 = (x1t * x1t) / (rx * rx) + (y1t * y1t) / (ry * ry);
- if (t1 > 1)
- {
- rx = rx * sqrtf(t1);
- ry = ry * sqrtf(t1);
- }
- /* F.6.5.2 */
- t1 = (rx * rx * ry * ry) - (rx * rx * y1t * y1t) - (ry * ry * x1t * x1t);
- t2 = (rx * rx * y1t * y1t) + (ry * ry * x1t * x1t);
- t3 = t1 / t2;
- /* guard against rounding errors; sqrt of negative numbers is bad for your health */
- if (t3 < 0) t3 = 0;
- t3 = sqrtf(t3);
- cxt = sign * t3 * (rx * y1t) / ry;
- cyt = sign * t3 * -(ry * x1t) / rx;
- /* F.6.5.3 */
- pt.x = cxt;
- pt.y = cyt;
- pt = fz_transform_vector(pt, rotmat);
- cx = pt.x + (x1 + x2) / 2;
- cy = pt.y + (y1 + y2) / 2;
- /* F.6.5.4 */
- {
- fz_point coord1, coord2, coord3, coord4;
- coord1.x = 1;
- coord1.y = 0;
- coord2.x = (x1t - cxt) / rx;
- coord2.y = (y1t - cyt) / ry;
- coord3.x = (x1t - cxt) / rx;
- coord3.y = (y1t - cyt) / ry;
- coord4.x = (-x1t - cxt) / rx;
- coord4.y = (-y1t - cyt) / ry;
- th1 = angle_between(coord1, coord2);
- dth = angle_between(coord3, coord4);
- if (dth < 0 && !is_clockwise)
- dth += ((FZ_PI / 180) * 360);
- if (dth > 0 && is_clockwise)
- dth -= ((FZ_PI / 180) * 360);
- }
- mtx = fz_pre_scale(fz_pre_rotate(fz_translate(cx, cy), rotation_angle), rx, ry);
- svg_add_arc_segment(ctx, path, mtx, th1, th1 + dth, is_clockwise);
- fz_lineto(ctx, path, point_x, point_y);
- }
- static void
- svg_parse_path_data(fz_context *ctx, fz_path *path, const char *str)
- {
- fz_point p;
- float x1, y1, x2, y2;
- int cmd;
- float number;
- float args[7];
- int nargs;
- /* saved control point for smooth curves */
- int reset_smooth = 1;
- float smooth_x = 0.0f;
- float smooth_y = 0.0f;
- cmd = 0;
- nargs = 0;
- fz_moveto(ctx, path, 0.0f, 0.0f); /* for the case of opening 'm' */
- while (*str)
- {
- while (svg_is_whitespace_or_comma(*str))
- str ++;
- /* arcto flag arguments are 1-character 0 or 1 */
- if ((cmd == 'a' || cmd == 'A') && (nargs == 3 || nargs == 4) && (*str == '0' || *str == '1'))
- {
- args[nargs++] = *str++ - '0';
- }
- else if (svg_is_digit(*str))
- {
- str = svg_lex_number(&number, str);
- if (nargs == nelem(args))
- {
- fz_warn(ctx, "stack overflow in path data");
- return;
- }
- args[nargs++] = number;
- }
- else if (svg_is_alpha(*str))
- {
- if (nargs != 0)
- {
- fz_warn(ctx, "syntax error in path data (wrong number of parameters to '%c')", cmd);
- return;
- }
- cmd = *str++;
- }
- else if (*str == 0)
- {
- return;
- }
- else
- {
- fz_warn(ctx, "syntax error in path data: '%c'", *str);
- return;
- }
- if (reset_smooth)
- {
- smooth_x = 0.0f;
- smooth_y = 0.0f;
- }
- reset_smooth = 1;
- switch (cmd)
- {
- case 'M':
- if (nargs == 2)
- {
- fz_moveto(ctx, path, args[0], args[1]);
- nargs = 0;
- cmd = 'L'; /* implicit lineto after */
- }
- break;
- case 'm':
- if (nargs == 2)
- {
- p = fz_currentpoint(ctx, path);
- fz_moveto(ctx, path, p.x + args[0], p.y + args[1]);
- nargs = 0;
- cmd = 'l'; /* implicit lineto after */
- }
- break;
- case 'Z':
- case 'z':
- if (nargs == 0)
- {
- fz_closepath(ctx, path);
- }
- break;
- case 'L':
- if (nargs == 2)
- {
- fz_lineto(ctx, path, args[0], args[1]);
- nargs = 0;
- }
- break;
- case 'l':
- if (nargs == 2)
- {
- p = fz_currentpoint(ctx, path);
- fz_lineto(ctx, path, p.x + args[0], p.y + args[1]);
- nargs = 0;
- }
- break;
- case 'H':
- if (nargs == 1)
- {
- p = fz_currentpoint(ctx, path);
- fz_lineto(ctx, path, args[0], p.y);
- nargs = 0;
- }
- break;
- case 'h':
- if (nargs == 1)
- {
- p = fz_currentpoint(ctx, path);
- fz_lineto(ctx, path, p.x + args[0], p.y);
- nargs = 0;
- }
- break;
- case 'V':
- if (nargs == 1)
- {
- p = fz_currentpoint(ctx, path);
- fz_lineto(ctx, path, p.x, args[0]);
- nargs = 0;
- }
- break;
- case 'v':
- if (nargs == 1)
- {
- p = fz_currentpoint(ctx, path);
- fz_lineto(ctx, path, p.x, p.y + args[0]);
- nargs = 0;
- }
- break;
- case 'C':
- reset_smooth = 0;
- if (nargs == 6)
- {
- fz_curveto(ctx, path, args[0], args[1], args[2], args[3], args[4], args[5]);
- smooth_x = args[4] - args[2];
- smooth_y = args[5] - args[3];
- nargs = 0;
- }
- break;
- case 'c':
- reset_smooth = 0;
- if (nargs == 6)
- {
- p = fz_currentpoint(ctx, path);
- fz_curveto(ctx, path,
- p.x + args[0], p.y + args[1],
- p.x + args[2], p.y + args[3],
- p.x + args[4], p.y + args[5]);
- smooth_x = args[4] - args[2];
- smooth_y = args[5] - args[3];
- nargs = 0;
- }
- break;
- case 'S':
- reset_smooth = 0;
- if (nargs == 4)
- {
- p = fz_currentpoint(ctx, path);
- fz_curveto(ctx, path,
- p.x + smooth_x, p.y + smooth_y,
- args[0], args[1],
- args[2], args[3]);
- smooth_x = args[2] - args[0];
- smooth_y = args[3] - args[1];
- nargs = 0;
- }
- break;
- case 's':
- reset_smooth = 0;
- if (nargs == 4)
- {
- p = fz_currentpoint(ctx, path);
- fz_curveto(ctx, path,
- p.x + smooth_x, p.y + smooth_y,
- p.x + args[0], p.y + args[1],
- p.x + args[2], p.y + args[3]);
- smooth_x = args[2] - args[0];
- smooth_y = args[3] - args[1];
- nargs = 0;
- }
- break;
- case 'Q':
- reset_smooth = 0;
- if (nargs == 4)
- {
- p = fz_currentpoint(ctx, path);
- x1 = args[0];
- y1 = args[1];
- x2 = args[2];
- y2 = args[3];
- fz_curveto(ctx, path,
- (p.x + 2 * x1) / 3, (p.y + 2 * y1) / 3,
- (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3,
- x2, y2);
- smooth_x = x2 - x1;
- smooth_y = y2 - y1;
- nargs = 0;
- }
- break;
- case 'q':
- reset_smooth = 0;
- if (nargs == 4)
- {
- p = fz_currentpoint(ctx, path);
- x1 = args[0] + p.x;
- y1 = args[1] + p.y;
- x2 = args[2] + p.x;
- y2 = args[3] + p.y;
- fz_curveto(ctx, path,
- (p.x + 2 * x1) / 3, (p.y + 2 * y1) / 3,
- (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3,
- x2, y2);
- smooth_x = x2 - x1;
- smooth_y = y2 - y1;
- nargs = 0;
- }
- break;
- case 'T':
- reset_smooth = 0;
- if (nargs == 2)
- {
- p = fz_currentpoint(ctx, path);
- x1 = p.x + smooth_x;
- y1 = p.y + smooth_y;
- x2 = args[0];
- y2 = args[1];
- fz_curveto(ctx, path,
- (p.x + 2 * x1) / 3, (p.y + 2 * y1) / 3,
- (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3,
- x2, y2);
- smooth_x = x2 - x1;
- smooth_y = y2 - y1;
- nargs = 0;
- }
- break;
- case 't':
- reset_smooth = 0;
- if (nargs == 2)
- {
- p = fz_currentpoint(ctx, path);
- x1 = p.x + smooth_x;
- y1 = p.y + smooth_y;
- x2 = args[0] + p.x;
- y2 = args[1] + p.y;
- fz_curveto(ctx, path,
- (p.x + 2 * x1) / 3, (p.y + 2 * y1) / 3,
- (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3,
- x2, y2);
- smooth_x = x2 - x1;
- smooth_y = y2 - y1;
- nargs = 0;
- }
- break;
- case 'A':
- if (nargs == 7)
- {
- svg_add_arc(ctx, path, args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
- nargs = 0;
- }
- break;
- case 'a':
- if (nargs == 7)
- {
- p = fz_currentpoint(ctx, path);
- svg_add_arc(ctx, path, args[0], args[1], args[2], args[3], args[4], args[5] + p.x, args[6] + p.y);
- nargs = 0;
- }
- break;
- case 0:
- if (nargs != 0)
- {
- fz_warn(ctx, "path data must begin with a command");
- return;
- }
- break;
- default:
- fz_warn(ctx, "unrecognized command in path data: '%c'", cmd);
- return;
- }
- }
- }
- static void
- svg_run_path(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
- {
- svg_state local_state;
- fz_path *path = NULL;
- const char *d_att = fz_xml_att(node, "d");
- /* unused: char *path_length_att = fz_xml_att(node, "pathLength"); */
- fz_var(path);
- fz_try(ctx)
- {
- svg_begin_state(ctx, &local_state, inherit_state);
- svg_parse_common(ctx, doc, node, &local_state);
- if (d_att)
- {
- path = fz_new_path(ctx);
- svg_parse_path_data(ctx, path, d_att);
- svg_draw_path(ctx, dev, doc, path, &local_state);
- }
- }
- fz_always(ctx)
- {
- fz_drop_path(ctx, path);
- svg_end_state(ctx, &local_state);
- }
- fz_catch(ctx)
- fz_rethrow(ctx);
- }
- /* svg, symbol, image, foreignObject establish new viewports */
- static void
- svg_parse_viewport(fz_context *ctx, svg_document *doc, fz_xml *node, svg_state *state)
- {
- char *w_att = fz_xml_att(node, "width");
- char *h_att = fz_xml_att(node, "height");
- if (w_att)
- state->viewport_w = svg_parse_length(w_att, state->viewbox_w, state->fontsize);
- if (h_att)
- state->viewport_h = svg_parse_length(h_att, state->viewbox_h, state->fontsize);
- }
- static void
- svg_lex_viewbox(const char *s, float *x, float *y, float *w, float *h)
- {
- *x = *y = *w = *h = 0;
- while (svg_is_whitespace_or_comma(*s)) ++s;
- if (svg_is_digit(*s)) s = svg_lex_number(x, s);
- while (svg_is_whitespace_or_comma(*s)) ++s;
- if (svg_is_digit(*s)) s = svg_lex_number(y, s);
- while (svg_is_whitespace_or_comma(*s)) ++s;
- if (svg_is_digit(*s)) s = svg_lex_number(w, s);
- while (svg_is_whitespace_or_comma(*s)) ++s;
- if (svg_is_digit(*s)) s = svg_lex_number(h, s);
- }
- static int
- svg_parse_preserve_aspect_ratio(const char *att, int *x, int *y)
- {
- *x = *y = 1;
- if (strstr(att, "none")) return 0;
- if (strstr(att, "xMin")) *x = 0;
- if (strstr(att, "xMid")) *x = 1;
- if (strstr(att, "xMax")) *x = 2;
- if (strstr(att, "YMin")) *y = 0;
- if (strstr(att, "YMid")) *y = 1;
- if (strstr(att, "YMax")) *y = 2;
- return 1;
- }
- /* svg, symbol, image, foreignObject plus marker, pattern, view can use viewBox to set the transform */
- static void
- svg_parse_viewbox(fz_context *ctx, svg_document *doc, fz_xml *node, svg_state *state)
- {
- char *viewbox_att = fz_xml_att(node, "viewBox");
- char *preserve_att = fz_xml_att(node, "preserveAspectRatio");
- if (viewbox_att)
- {
- /* scale and translate to fit [minx miny minx+w miny+h] to [0 0 viewport.w viewport.h] */
- float min_x, min_y, box_w, box_h, sx, sy;
- int align_x=1, align_y=1, preserve=1;
- float pad_x=0, pad_y=0;
- svg_lex_viewbox(viewbox_att, &min_x, &min_y, &box_w, &box_h);
- sx = state->viewport_w / box_w;
- sy = state->viewport_h / box_h;
- if (preserve_att)
- preserve = svg_parse_preserve_aspect_ratio(preserve_att, &align_x, &align_y);
- if (preserve)
- {
- sx = sy = fz_min(sx, sy);
- if (align_x == 1) pad_x = (box_w * sx - state->viewport_w) / 2;
- if (align_x == 2) pad_x = (box_w * sx - state->viewport_w);
- if (align_y == 1) pad_y = (box_h * sy - state->viewport_h) / 2;
- if (align_y == 2) pad_y = (box_h * sy - state->viewport_h);
- state->transform = fz_concat(fz_translate(-pad_x, -pad_y), state->transform);
- }
- state->transform = fz_concat(fz_scale(sx, sy), state->transform);
- state->transform = fz_concat(fz_translate(-min_x, -min_y), state->transform);
- state->viewbox_w = box_w;
- state->viewbox_h = box_h;
- state->viewbox_size = sqrtf(box_w*box_w + box_h*box_h) / sqrtf(2);
- }
- }
- static const char *linecap_table[] = { "butt", "round", "square" };
- static const char *linejoin_table[] = { "miter", "round", "bevel" };
- /* parse transform and presentation attributes */
- static void
- svg_parse_common(fz_context *ctx, svg_document *doc, fz_xml *node, svg_state *state)
- {
- fz_stroke_state *stroke = state->stroke;
- char *transform_att = fz_xml_att(node, "transform");
- char *font_size_att = fz_xml_att(node, "font-size");
- char *style_att = fz_xml_att(node, "style");
- // TODO: clip, clip-path, clip-rule
- char *opacity_att = fz_xml_att(node, "opacity");
- char *fill_att = fz_xml_att(node, "fill");
- char *fill_rule_att = fz_xml_att(node, "fill-rule");
- char *fill_opacity_att = fz_xml_att(node, "fill-opacity");
- char *stroke_att = fz_xml_att(node, "stroke");
- char *stroke_opacity_att = fz_xml_att(node, "stroke-opacity");
- char *stroke_width_att = fz_xml_att(node, "stroke-width");
- char *stroke_linecap_att = fz_xml_att(node, "stroke-linecap");
- char *stroke_linejoin_att = fz_xml_att(node, "stroke-linejoin");
- char *stroke_miterlimit_att = fz_xml_att(node, "stroke-miterlimit");
- // TODO: stroke-dasharray, stroke-dashoffset
- // TODO: marker, marker-start, marker-mid, marker-end
- // TODO: overflow
- // TODO: mask
- /* Dirty hack scans of CSS style */
- if (style_att)
- {
- svg_parse_color_from_style(ctx, doc, style_att,
- &state->fill_is_set, state->fill_color,
- &state->stroke_is_set, state->stroke_color);
- }
- if (transform_att)
- {
- state->transform = svg_parse_transform(ctx, doc, transform_att, state->transform);
- }
- if (font_size_att)
- {
- state->fontsize = svg_parse_length(font_size_att, state->fontsize, state->fontsize);
- }
- else
- {
- state->fontsize = svg_parse_number_from_style(ctx, doc, style_att, "font-size", state->fontsize);
- }
- if (opacity_att)
- {
- state->opacity = svg_parse_number(opacity_att, 0, 1, state->opacity);
- }
- if (fill_att)
- {
- if (!strcmp(fill_att, "none"))
- {
- state->fill_is_set = 0;
- }
- else
- {
- state->fill_is_set = 1;
- svg_parse_color(ctx, doc, fill_att, state->fill_color);
- }
- }
- if (fill_opacity_att)
- state->fill_opacity = svg_parse_number(fill_opacity_att, 0, 1, state->fill_opacity);
- if (fill_rule_att)
- {
- if (!strcmp(fill_rule_att, "nonzero"))
- state->fill_rule = 0;
- if (!strcmp(fill_rule_att, "evenodd"))
- state->fill_rule = 1;
- }
- if (stroke_att)
- {
- if (!strcmp(stroke_att, "none"))
- {
- state->stroke_is_set = 0;
- }
- else
- {
- state->stroke_is_set = 1;
- svg_parse_color(ctx, doc, stroke_att, state->stroke_color);
- }
- }
- if (stroke_opacity_att)
- state->stroke_opacity = svg_parse_number(stroke_opacity_att, 0, 1, state->stroke_opacity);
- if (stroke_width_att)
- {
- if (!strcmp(stroke_width_att, "inherit"))
- ;
- else
- stroke->linewidth = svg_parse_length(stroke_width_att, state->viewbox_size, state->fontsize);
- }
- else
- {
- stroke->linewidth = svg_parse_number_from_style(ctx, doc, style_att, "stroke-width", state->stroke->linewidth);
- }
- if (stroke_linecap_att)
- {
- if (!strcmp(stroke_linecap_att, "butt"))
- stroke->start_cap = FZ_LINECAP_BUTT;
- if (!strcmp(stroke_linecap_att, "round"))
- stroke->start_cap = FZ_LINECAP_ROUND;
- if (!strcmp(stroke_linecap_att, "square"))
- stroke->start_cap = FZ_LINECAP_SQUARE;
- }
- else
- {
- stroke->start_cap = svg_parse_enum_from_style(ctx, doc, style_att, "stroke-linecap",
- nelem(linecap_table), linecap_table, FZ_LINECAP_BUTT);
- }
- stroke->dash_cap = stroke->start_cap;
- stroke->end_cap = stroke->start_cap;
- if (stroke_linejoin_att)
- {
- if (!strcmp(stroke_linejoin_att, "miter"))
- stroke->linejoin = FZ_LINEJOIN_MITER;
- if (!strcmp(stroke_linejoin_att, "round"))
- stroke->linejoin = FZ_LINEJOIN_ROUND;
- if (!strcmp(stroke_linejoin_att, "bevel"))
- stroke->linejoin = FZ_LINEJOIN_BEVEL;
- }
- else
- {
- stroke->linejoin = svg_parse_enum_from_style(ctx, doc, style_att, "stroke-linejoin",
- nelem(linejoin_table), linejoin_table, FZ_LINEJOIN_MITER);
- }
- if (stroke_miterlimit_att)
- {
- if (!strcmp(stroke_miterlimit_att, "inherit"))
- ;
- else
- stroke->miterlimit = svg_parse_length(stroke_miterlimit_att, state->viewbox_size, state->fontsize);
- }
- else
- {
- stroke->miterlimit = svg_parse_number_from_style(ctx, doc, style_att, "stroke-miterlimit", state->stroke->miterlimit);
- }
- }
- static void
- svg_parse_font_attributes(fz_context *ctx, svg_document *doc, fz_xml *node, svg_state *state, char *buf, int buf_size)
- {
- char *style_att = fz_xml_att(node, "style");
- char *font_family_att = fz_xml_att(node, "font-family");
- char *font_weight_att = fz_xml_att(node, "font-weight");
- char *font_style_att = fz_xml_att(node, "font-style");
- char *text_anchor_att = fz_xml_att(node, "text-anchor");
- if (font_family_att)
- fz_strlcpy(buf, font_family_att, buf_size);
- else
- svg_parse_string_from_style(ctx, doc, style_att, "font-family", buf, buf_size, state->font_family);
- state->font_family = buf;
- if (font_weight_att)
- {
- state->is_bold = atoi(font_weight_att) > 400;
- if (!strcmp(font_weight_att, "bold")) state->is_bold = 1;
- if (!strcmp(font_weight_att, "bolder")) state->is_bold = 1;
- }
- else
- {
- static const char *is_bold_table[] = {
- "normal", "100", "200", "300", "400", "bold", "bolder", "500", "600", "700", "800", "900"
- };
- state->is_bold = svg_parse_enum_from_style(ctx, doc, style_att, "font-weight",
- nelem(is_bold_table), is_bold_table, state->is_bold ? 5 : 0) >= 5;
- }
- if (font_style_att)
- {
- state->is_italic = 0;
- if (!strcmp(font_style_att, "italic")) state->is_italic = 1;
- if (!strcmp(font_style_att, "oblique")) state->is_italic = 1;
- }
- else
- {
- static const char *is_italic_table[] = {
- "normal", "italic", "oblique"
- };
- state->is_italic = svg_parse_enum_from_style(ctx, doc, style_att, "font-style",
- nelem(is_italic_table), is_italic_table, state->is_italic) >= 1;
- }
- if (text_anchor_att)
- {
- state->text_anchor = 0;
- if (!strcmp(text_anchor_att, "middle")) state->text_anchor = 1;
- if (!strcmp(text_anchor_att, "end")) state->text_anchor = 2;
- }
- else
- {
- static const char *text_anchor_table[] = {
- "start", "middle", "end"
- };
- state->text_anchor = svg_parse_enum_from_style(ctx, doc, style_att, "text-anchor",
- nelem(text_anchor_table), text_anchor_table, state->text_anchor);
- }
- }
- static void
- svg_run_svg(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *inherit_state)
- {
- svg_state local_state;
- fz_xml *node;
- char *w_att = fz_xml_att(root, "width");
- char *h_att = fz_xml_att(root, "height");
- char *viewbox_att = fz_xml_att(root, "viewBox");
- fz_try(ctx)
- {
- svg_begin_state(ctx, &local_state, inherit_state);
- /* get default viewport from viewBox if width and/or height is missing */
- if (viewbox_att && (!w_att || !h_att))
- {
- float x, y;
- svg_lex_viewbox(viewbox_att, &x, &y, &local_state.viewbox_w, &local_state.viewbox_h);
- if (!w_att) local_state.viewport_w = local_state.viewbox_w;
- if (!h_att) local_state.viewport_h = local_state.viewbox_h;
- }
- svg_parse_viewport(ctx, doc, root, &local_state);
- svg_parse_viewbox(ctx, doc, root, &local_state);
- svg_parse_common(ctx, doc, root, &local_state);
- for (node = fz_xml_down(root); node; node = fz_xml_next(node))
- svg_run_element(ctx, dev, doc, node, &local_state);
- }
- fz_always(ctx)
- svg_end_state(ctx, &local_state);
- fz_catch(ctx)
- fz_rethrow(ctx);
- }
- static void
- svg_run_g(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *inherit_state)
- {
- svg_state local_state;
- fz_xml *node;
- fz_try(ctx)
- {
- svg_begin_state(ctx, &local_state, inherit_state);
- svg_parse_common(ctx, doc, root, &local_state);
- for (node = fz_xml_down(root); node; node = fz_xml_next(node))
- svg_run_element(ctx, dev, doc, node, &local_state);
- }
- fz_always(ctx)
- svg_end_state(ctx, &local_state);
- fz_catch(ctx)
- fz_rethrow(ctx);
- }
- static void
- svg_run_use_symbol(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *use, fz_xml *symbol, const svg_state *inherit_state)
- {
- svg_state local_state;
- fz_xml *node;
- fz_try(ctx)
- {
- svg_begin_state(ctx, &local_state, inherit_state);
- svg_parse_viewport(ctx, doc, use, &local_state);
- svg_parse_viewbox(ctx, doc, use, &local_state);
- for (node = fz_xml_down(symbol); node; node = fz_xml_next(node))
- svg_run_element(ctx, dev, doc, node, &local_state);
- }
- fz_always(ctx)
- svg_end_state(ctx, &local_state);
- fz_catch(ctx)
- fz_rethrow(ctx);
- }
- static int
- is_use_cycle(fz_xml *use, fz_xml *symbol)
- {
- /* If "use" is a direct child of "symbol", we have a recursive symbol/use definition! */
- while (use)
- {
- if (use == symbol)
- return 1;
- use = fz_xml_up(use);
- }
- return 0;
- }
- static void
- svg_run_use(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *inherit_state)
- {
- svg_state local_state;
- char *href_att = fz_xml_att_alt(root, "xlink:href", "href");
- char *x_att = fz_xml_att(root, "x");
- char *y_att = fz_xml_att(root, "y");
- fz_xml *linked = NULL;
- float x = 0;
- float y = 0;
- fz_try(ctx)
- {
- svg_begin_state(ctx, &local_state, inherit_state);
- if (++local_state.use_depth > MAX_USE_DEPTH)
- {
- fz_warn(ctx, "svg: too much recursion");
- break;
- }
- svg_parse_common(ctx, doc, root, &local_state);
- if (x_att) x = svg_parse_length(x_att, local_state.viewbox_w, local_state.fontsize);
- if (y_att) y = svg_parse_length(y_att, local_state.viewbox_h, local_state.fontsize);
- local_state.transform = fz_concat(fz_translate(x, y), local_state.transform);
- if (href_att && href_att[0] == '#')
- {
- linked = fz_tree_lookup(ctx, doc->idmap, href_att + 1);
- if (linked)
- {
- if (is_use_cycle(root, linked))
- fz_warn(ctx, "svg: cyclic <use> reference");
- if (fz_xml_is_tag(linked, "symbol"))
- svg_run_use_symbol(ctx, dev, doc, root, linked, &local_state);
- else
- svg_run_element(ctx, dev, doc, linked, &local_state);
- }
- else
- {
- fz_warn(ctx, "svg: cannot find linked symbol");
- }
- }
- else
- {
- fz_warn(ctx, "svg: cannot find linked symbol");
- }
- }
- fz_always(ctx)
- svg_end_state(ctx, &local_state);
- fz_catch(ctx)
- fz_rethrow(ctx);
- }
- static void
- svg_run_image(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *inherit_state)
- {
- svg_state local_state;
- float x=0, y=0, w=0, h=0;
- const char *data;
- static const char *jpeg_uri = "data:image/jpeg;base64,";
- static const char *png_uri = "data:image/png;base64,";
- char *href_att = fz_xml_att_alt(root, "xlink:href", "href");
- char *x_att = fz_xml_att(root, "x");
- char *y_att = fz_xml_att(root, "y");
- char *w_att = fz_xml_att(root, "width");
- char *h_att = fz_xml_att(root, "height");
- fz_try(ctx)
- {
- svg_begin_state(ctx, &local_state, inherit_state);
- svg_parse_common(ctx, doc, root, &local_state);
- if (x_att) x = svg_parse_length(x_att, local_state.viewbox_w, local_state.fontsize);
- if (y_att) y = svg_parse_length(y_att, local_state.viewbox_h, local_state.fontsize);
- if (w_att) w = svg_parse_length(w_att, local_state.viewbox_w, local_state.fontsize);
- if (h_att) h = svg_parse_length(h_att, local_state.viewbox_h, local_state.fontsize);
- if (w <= 0 || h <= 0)
- break; // out of try-catch
- if (!href_att)
- break; // out of try-catch
- local_state.transform = fz_concat(fz_translate(x, y), local_state.transform);
- local_state.transform = fz_concat(fz_scale(w, h), local_state.transform);
- if (!strncmp(href_att, jpeg_uri, strlen(jpeg_uri)))
- data = href_att + strlen(jpeg_uri);
- else if (!strncmp(href_att, png_uri, strlen(png_uri)))
- data = href_att + strlen(png_uri);
- else
- data = NULL;
- if (data)
- {
- fz_image *img = NULL;
- fz_buffer *buf;
- fz_var(img);
- buf = fz_new_buffer_from_base64(ctx, data, 0);
- fz_try(ctx)
- {
- fz_matrix orient;
- img = fz_new_image_from_buffer(ctx, buf);
- orient = fz_image_orientation_matrix(ctx, img);
- local_state.transform = fz_concat(orient, local_state.transform);
- fz_fill_image(ctx, dev, img, local_state.transform, 1, fz_default_color_params);
- }
- fz_always(ctx)
- {
- fz_drop_buffer(ctx, buf);
- fz_drop_image(ctx, img);
- }
- fz_catch(ctx)
- {
- fz_rethrow_if(ctx, FZ_ERROR_SYSTEM);
- fz_report_error(ctx);
- fz_warn(ctx, "svg: ignoring embedded image '%s'", href_att);
- }
- }
- else if (doc->zip)
- {
- char path[2048];
- fz_buffer *buf = NULL;
- fz_image *img = NULL;
- fz_var(buf);
- fz_var(img);
- fz_strlcpy(path, doc->base_uri, sizeof path);
- fz_strlcat(path, "/", sizeof path);
- fz_strlcat(path, href_att, sizeof path);
- fz_urldecode(path);
- fz_try(ctx)
- {
- fz_matrix orient;
- buf = fz_read_archive_entry(ctx, doc->zip, path);
- img = fz_new_image_from_buffer(ctx, buf);
- orient = fz_image_orientation_matrix(ctx, img);
- local_state.transform = fz_concat(orient, local_state.transform);
- fz_fill_image(ctx, dev, img, local_state.transform, 1, fz_default_color_params);
- }
- fz_always(ctx)
- {
- fz_drop_buffer(ctx, buf);
- fz_drop_image(ctx, img);
- }
- fz_catch(ctx)
- {
- fz_rethrow_if(ctx, FZ_ERROR_SYSTEM);
- fz_report_error(ctx);
- fz_warn(ctx, "svg: ignoring external image '%s'", href_att);
- }
- }
- else
- {
- fz_warn(ctx, "svg: ignoring external image '%s'", href_att);
- }
- }
- fz_always(ctx)
- svg_end_state(ctx, &local_state);
- fz_catch(ctx)
- fz_rethrow(ctx);
- }
- static fz_font *
- svg_load_font(fz_context *ctx, const svg_state *state)
- {
- int bold = state->is_bold;
- int italic = state->is_italic;
- int mono = 0;
- int serif = 1;
- /* scan font-family property for common fallback names */
- if (!mono && strstr(state->font_family, "monospace")) mono = 1;
- if (!mono && strstr(state->font_family, "Courier")) mono = 1;
- if (serif && strstr(state->font_family, "sans-serif")) serif = 0;
- if (serif && strstr(state->font_family, "Arial")) serif = 0;
- if (serif && strstr(state->font_family, "Helvetica")) serif = 0;
- if (mono) {
- if (bold) {
- if (italic) return fz_new_base14_font(ctx, "Courier-BoldOblique");
- else return fz_new_base14_font(ctx, "Courier-Bold");
- } else {
- if (italic) return fz_new_base14_font(ctx, "Courier-Oblique");
- else return fz_new_base14_font(ctx, "Courier");
- }
- } else if (serif) {
- if (bold) {
- if (italic) return fz_new_base14_font(ctx, "Times-BoldItalic");
- else return fz_new_base14_font(ctx, "Times-Bold");
- } else {
- if (italic) return fz_new_base14_font(ctx, "Times-Italic");
- else return fz_new_base14_font(ctx, "Times-Roman");
- }
- } else {
- if (bold) {
- if (italic) return fz_new_base14_font(ctx, "Helvetica-BoldOblique");
- else return fz_new_base14_font(ctx, "Helvetica-Bold");
- } else {
- if (italic) return fz_new_base14_font(ctx, "Helvetica-Oblique");
- else return fz_new_base14_font(ctx, "Helvetica");
- }
- }
- }
- static fz_matrix
- svg_run_text_string(fz_context *ctx, fz_device *dev, fz_matrix trm, const char *s, const svg_state *state)
- {
- fz_font *font = NULL;
- fz_text *text = NULL;
- fz_var(font);
- fz_var(text);
- fz_try(ctx)
- {
- font = svg_load_font(ctx, state);
- text = fz_new_text(ctx);
- if (state->text_anchor > 0)
- {
- fz_matrix adv = fz_measure_string(ctx, font, trm, s, 0, 0, FZ_BIDI_LTR, FZ_LANG_UNSET);
- if (state->text_anchor == 1)
- trm.e -= (adv.e - trm.e) / 2;
- else if (state->text_anchor == 2)
- trm.e -= (adv.e - trm.e);
- }
- trm = fz_show_string(ctx, text, font, trm, s, 0, 0, FZ_BIDI_LTR, FZ_LANG_UNSET);
- if (state->fill_is_set)
- fz_fill_text(ctx, dev, text,
- state->transform,
- fz_device_rgb(ctx), state->fill_color,
- state->opacity,
- fz_default_color_params);
- if (state->stroke_is_set)
- fz_stroke_text(ctx, dev, text,
- state->stroke,
- state->transform,
- fz_device_rgb(ctx), state->stroke_color,
- state->opacity,
- fz_default_color_params);
- if (!state->fill_is_set && !state->stroke_is_set)
- fz_ignore_text(ctx, dev, text, state->transform);
- }
- fz_always(ctx)
- {
- fz_drop_text(ctx, text);
- fz_drop_font(ctx, font);
- }
- fz_catch(ctx)
- {
- fz_rethrow(ctx);
- }
- return trm;
- }
- static void
- svg_collapse_whitespace(char *start, int is_first, int is_last)
- {
- int c, last_c = (is_first ? ' ' : 0);
- char *s, *p;
- s = p = start;
- while ((c = *s++) != 0)
- {
- if (c == '\n' || c == '\r')
- continue;
- if (c == '\t')
- c = ' ';
- if (c == ' ' && last_c == ' ')
- continue;
- *p++ = last_c = c;
- }
- if (is_last && p > start && p[-1] == ' ')
- --p;
- *p = 0;
- }
- static fz_matrix
- svg_run_text(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *inherit_state,
- float x, float y, int is_first, int is_last)
- {
- svg_state local_state;
- char font_family[100];
- fz_xml *node;
- fz_matrix trm;
- int cif, cil;
- char *text;
- char *x_att = fz_xml_att(root, "x");
- char *y_att = fz_xml_att(root, "y");
- char *dx_att = fz_xml_att(root, "dx");
- char *dy_att = fz_xml_att(root, "dy");
- fz_try(ctx)
- {
- svg_begin_state(ctx, &local_state, inherit_state);
- svg_parse_common(ctx, doc, root, &local_state);
- svg_parse_font_attributes(ctx, doc, root, &local_state, font_family, sizeof font_family);
- trm = fz_scale(local_state.fontsize, -local_state.fontsize);
- trm.e = x;
- trm.f = y;
- if (x_att) trm.e = svg_parse_length(x_att, local_state.viewbox_w, local_state.fontsize);
- if (y_att) trm.f = svg_parse_length(y_att, local_state.viewbox_h, local_state.fontsize);
- if (dx_att) trm.e += svg_parse_length(dx_att, local_state.viewbox_w, local_state.fontsize);
- if (dy_att) trm.f += svg_parse_length(dy_att, local_state.viewbox_h, local_state.fontsize);
- cif = is_first;
- for (node = fz_xml_down(root); node; node = fz_xml_next(node))
- {
- cil = is_last && !fz_xml_next(node);
- text = fz_xml_text(node);
- if (text)
- {
- svg_collapse_whitespace(text, cif, cil);
- trm = svg_run_text_string(ctx, dev, trm, text, &local_state);
- }
- else if (fz_xml_is_tag(node, "tspan"))
- trm = svg_run_text(ctx, dev, doc, node, &local_state, trm.e, trm.f, cif, cil);
- else if (fz_xml_is_tag(node, "textPath"))
- trm = svg_run_text(ctx, dev, doc, node, &local_state, trm.e, trm.f, cif, cil);
- cif = 0;
- }
- }
- fz_always(ctx)
- svg_end_state(ctx, &local_state);
- fz_catch(ctx)
- fz_rethrow(ctx);
- return trm;
- }
- static void
- svg_run_element(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *state)
- {
- if (fz_xml_is_tag(root, "svg"))
- svg_run_svg(ctx, dev, doc, root, state);
- else if (fz_xml_is_tag(root, "g"))
- svg_run_g(ctx, dev, doc, root, state);
- else if (fz_xml_is_tag(root, "title"))
- ;
- else if (fz_xml_is_tag(root, "desc"))
- ;
- else if (fz_xml_is_tag(root, "defs"))
- ;
- else if (fz_xml_is_tag(root, "symbol"))
- ;
- else if (fz_xml_is_tag(root, "use"))
- svg_run_use(ctx, dev, doc, root, state);
- else if (fz_xml_is_tag(root, "path"))
- svg_run_path(ctx, dev, doc, root, state);
- else if (fz_xml_is_tag(root, "rect"))
- svg_run_rect(ctx, dev, doc, root, state);
- else if (fz_xml_is_tag(root, "circle"))
- svg_run_circle(ctx, dev, doc, root, state);
- else if (fz_xml_is_tag(root, "ellipse"))
- svg_run_ellipse(ctx, dev, doc, root, state);
- else if (fz_xml_is_tag(root, "line"))
- svg_run_line(ctx, dev, doc, root, state);
- else if (fz_xml_is_tag(root, "polyline"))
- svg_run_polyline(ctx, dev, doc, root, state);
- else if (fz_xml_is_tag(root, "polygon"))
- svg_run_polygon(ctx, dev, doc, root, state);
- else if (fz_xml_is_tag(root, "image"))
- svg_run_image(ctx, dev, doc, root, state);
- else if (fz_xml_is_tag(root, "text"))
- svg_run_text(ctx, dev, doc, root, state, 0, 0, 1, 1);
- else
- {
- /* ignore unrecognized tags */
- }
- }
- void
- svg_parse_document_bounds(fz_context *ctx, svg_document *doc, fz_xml *root)
- {
- char *version_att;
- char *w_att;
- char *h_att;
- char *viewbox_att;
- int version;
- if (!fz_xml_is_tag(root, "svg"))
- fz_throw(ctx, FZ_ERROR_SYNTAX, "expected svg element (found %s)", fz_xml_tag(root));
- version_att = fz_xml_att(root, "version");
- w_att = fz_xml_att(root, "width");
- h_att = fz_xml_att(root, "height");
- viewbox_att = fz_xml_att(root, "viewBox");
- version = 10;
- if (version_att)
- version = fz_atof(version_att) * 10;
- if (version > 12)
- fz_warn(ctx, "svg document version is newer than we support");
- /* If no width or height attributes, then guess from the viewbox */
- if (w_att == NULL && h_att == NULL && viewbox_att != NULL)
- {
- float min_x, min_y, box_w, box_h;
- svg_lex_viewbox(viewbox_att, &min_x, &min_y, &box_w, &box_h);
- doc->width = box_w;
- doc->height = box_h;
- }
- else
- {
- doc->width = DEF_WIDTH;
- if (w_att)
- doc->width = svg_parse_length(w_att, doc->width, DEF_FONTSIZE);
- doc->height = DEF_HEIGHT;
- if (h_att)
- doc->height = svg_parse_length(h_att, doc->height, DEF_FONTSIZE);
- }
- }
- void
- svg_run_document(fz_context *ctx, svg_document *doc, fz_xml *root, fz_device *dev, fz_matrix ctm)
- {
- svg_state state;
- svg_parse_document_bounds(ctx, doc, root);
- /* Initial graphics state */
- state.transform = ctm;
- state.stroke = fz_new_stroke_state(ctx);
- state.use_depth = 0;
- state.viewport_w = DEF_WIDTH;
- state.viewport_h = DEF_HEIGHT;
- state.viewbox_w = DEF_WIDTH;
- state.viewbox_h = DEF_HEIGHT;
- state.viewbox_size = sqrtf(DEF_WIDTH*DEF_WIDTH + DEF_HEIGHT*DEF_HEIGHT) / sqrtf(2);
- state.fontsize = 12;
- state.opacity = 1;
- state.fill_rule = 0;
- state.fill_is_set = 1;
- state.fill_color[0] = 0;
- state.fill_color[1] = 0;
- state.fill_color[2] = 0;
- state.fill_opacity = 1;
- state.stroke_is_set = 0;
- state.stroke_color[0] = 0;
- state.stroke_color[1] = 0;
- state.stroke_color[2] = 0;
- state.stroke_opacity = 1;
- state.font_family = "serif";
- state.is_bold = 0;
- state.is_italic = 0;
- state.text_anchor = 0;
- fz_try(ctx)
- {
- svg_run_svg(ctx, dev, doc, root, &state);
- }
- fz_always(ctx)
- {
- fz_drop_stroke_state(ctx, state.stroke);
- }
- fz_catch(ctx)
- {
- fz_rethrow(ctx);
- }
- }
|