SVG Reader – Case 2 (Path + Transform + ViewBox + Zoom/Pan/Rotate)
Mục tiêu: Mở rộng Case 1 để hỗ trợ <path> với các lệnh M/L/H/V/C/Q/Z (subset),
hỗ trợ transform (translate/scale/rotate), parse viewBox và fit-to-window, tương tác
zoom/pan/rotate.
1) Bổ sung lớp SvgPath
class SvgPath : public SvgElement {
public:
mutable std::unique_ptr<Gdiplus::GraphicsPath> cache;
std::wstring d;
void buildPath() const {
if (cache) return;
cache = std::make_unique<Gdiplus::GraphicsPath>();
size_t i = 0;
auto skipws = [&](void){ while(i<[Link]() && iswspace(d[i])) ++i; };
auto readNum = [&](double& out)->bool{
skipws(); std::wstring s;
while(i<[Link]() && (iswdigit(d[i]) || d[i]==L'.' || d[i]==L'-' ||
d[i]==L'+')) s += d[i++];
if([Link]()) return false;
out = std::stod(s); return true;
};
double x=0,y=0, sx=0, sy=0;
while(i < [Link]()) {
skipws();
if (i>=[Link]()) break;
wchar_t cmd = d[i++];
if (cmd == 'M' || cmd == 'm') {
double nx, ny; if(readNum(nx) && readNum(ny)){
if (cmd=='m') { x += nx; y += ny; } else { x = nx; y =
ny; }
cache->StartFigure();
cache->AddLine((Gdiplus::REAL)x,(Gdiplus::REAL)y,
(Gdiplus::REAL)x,(Gdiplus::REAL)y);
sx = x; sy = y;
}
} else if (cmd == 'L' || cmd == 'l') {
double nx, ny; if(readNum(nx) && readNum(ny)){
if (cmd=='l') { nx += x; ny += y; }
cache->AddLine((Gdiplus::REAL)x,(Gdiplus::REAL)y,
(Gdiplus::REAL)nx,(Gdiplus::REAL)ny);
x = nx; y = ny;
}
} else if (cmd == 'Z' || cmd == 'z') {
cache->CloseFigure();
x = sx; y = sy;
} else {
// TODO: H/V/C/Q
}
}
}
void render(Gdiplus::Graphics& g, const RenderContext& ctx) const override
{
buildPath();
Gdiplus::Matrix old; [Link](&old);
Gdiplus::Matrix m = old; [Link](&transform);
[Link](&m);
Gdiplus::SolidBrush fb(toGdiColor(fill));
Gdiplus::Pen sp(toGdiColor(stroke), strokeWidth);
if (cache) {
[Link](&fb, [Link]());
[Link](&sp, [Link]());
}
[Link](&old);
}
};
2) Transform & kế thừa style
struct TransformUtil {
static void applyTranslate(Gdiplus::Matrix& m, float tx, float ty)
{ [Link](tx, ty, Gdiplus::MatrixOrderAppend); }
static void applyScale(Gdiplus::Matrix& m, float sx, float sy){ [Link](sx,
sy, Gdiplus::MatrixOrderAppend); }
static void applyRotate(Gdiplus::Matrix& m, float deg){ [Link](deg,
Gdiplus::MatrixOrderAppend); }
};
3) ViewBox & Fit-to-Window
void computeFitMatrix(Gdiplus::Matrix& world, float cw, float ch, float vx,
float vy, float vw, float vh){
float sx = cw / vw, sy = ch / vh;
float s = (sx < sy) ? sx : sy;
[Link]();
[Link]((cw - s*vw)*0.5f, (ch - s*vh)*0.5f,
Gdiplus::MatrixOrderAppend);
[Link](s, s, Gdiplus::MatrixOrderAppend);
[Link](-vx, -vy, Gdiplus::MatrixOrderAppend);
}
4) Tương tác: Zoom/Pan/Rotate
// WndProc: xửlý WM_MOUSEWHEEL, WM_LBUTTONDOWN, WM_MOUSEMOVE, WM_KEYDOWN
(R/Shift+R).