Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
247 changes: 245 additions & 2 deletions src/renderer/skia/renderer.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use skia_safe::{
font, paint, surfaces, Canvas, Color, EncodedImageFormat, Font, FontMgr, FontStyle, Paint,
PathBuilder, PathEffect, Rect, Typeface,
PathBuilder, PathEffect, RRect, Rect, Typeface,
};
use std::collections::HashMap;

Expand Down Expand Up @@ -755,7 +755,7 @@ impl SkiaLayerRenderer {
canvas.restore();
}
PaintOp::FormObject { bbox, form } => {
draw_placeholder(*bbox, form.caption.as_str());
self.draw_form_control(canvas, *bbox, form);
}
Comment on lines 757 to 759
PaintOp::Placeholder { bbox, placeholder } => {
draw_placeholder(*bbox, placeholder.label.as_str());
Expand All @@ -778,6 +778,249 @@ impl LayerRasterRenderer for SkiaLayerRenderer {
}
}

impl SkiaLayerRenderer {
fn make_form_font(&self, size: f32) -> Font {
let style = FontStyle::default();
let cjk_families = ["Malgun Gothic", "맑은 고딕", "NanumGothic", "나눔고딕", "AppleGothic"];
for family in &cjk_families {
if let Some(tf) = self.custom_typefaces.get(*family).cloned() {
return Font::new(tf, size);
}
if let Some(tf) = self.font_mgr.match_family_style(family, style) {
return Font::new(tf, size);
}
}
if let Some(tf) = self.font_mgr.legacy_make_typeface(None::<&str>, style) {
return Font::new(tf, size);
}
let mut f = Font::default();
f.set_size(size);
f
}

fn draw_form_control(
&self,
canvas: &Canvas,
bbox: crate::renderer::render_tree::BoundingBox,
form: &crate::renderer::render_tree::FormObjectNode,
) {
use crate::model::control::FormType;

if bbox.width <= 0.0 || bbox.height <= 0.0 {
return;
}

let x = bbox.x as f32;
let y = bbox.y as f32;
let w = bbox.width as f32;
let h = bbox.height as f32;
let rect = Rect::from_xywh(x, y, w, h);

let bg_color = parse_css_color(&form.back_color).unwrap_or(Color::from_rgb(240, 240, 240));
let fg_color = parse_css_color(&form.fore_color).unwrap_or(Color::from_rgb(0, 0, 0));
let border_color = Color::from_rgb(160, 160, 160);

match form.form_type {
FormType::PushButton => {
let mut fill = Paint::default();
fill.set_anti_alias(true);
fill.set_style(paint::Style::Fill);
fill.set_color(bg_color);
let rrect = RRect::new_rect_xy(rect, 3.0, 3.0);
canvas.draw_rrect(rrect, &fill);
Comment on lines +829 to +830

let mut stroke = Paint::default();
stroke.set_anti_alias(true);
stroke.set_style(paint::Style::Stroke);
stroke.set_stroke_width(1.0);
stroke.set_color(border_color);
canvas.draw_rrect(rrect, &stroke);

let label = if form.caption.is_empty() { &form.name } else { &form.caption };
if !label.is_empty() {
let font = self.make_form_font((h * 0.45).clamp(8.0, 14.0));
let mut tp = Paint::default();
tp.set_anti_alias(true);
tp.set_color(fg_color);
let text_w = font.measure_str(label, Some(&tp)).0;
let tx = x + (w - text_w) / 2.0;
let ty = y + h / 2.0 + font.size() * 0.35;
canvas.draw_str(label, (tx, ty), &font, &tp);
}
}
FormType::CheckBox => {
let box_size = h.min(w).min(14.0);
let bx = x + 2.0;
let by = y + (h - box_size) / 2.0;
let box_rect = Rect::from_xywh(bx, by, box_size, box_size);

let mut fill = Paint::default();
fill.set_anti_alias(true);
fill.set_style(paint::Style::Fill);
fill.set_color(bg_color);
canvas.draw_rect(box_rect, &fill);

let mut stroke = Paint::default();
stroke.set_anti_alias(true);
stroke.set_style(paint::Style::Stroke);
stroke.set_stroke_width(1.0);
stroke.set_color(border_color);
canvas.draw_rect(box_rect, &stroke);

if form.value != 0 {
let mut check = Paint::default();
check.set_anti_alias(true);
check.set_style(paint::Style::Stroke);
check.set_stroke_width(2.0);
check.set_color(fg_color);
check.set_stroke_cap(paint::Cap::Round);
let cx = bx + box_size * 0.2;
let cy = by + box_size * 0.55;
let mx = bx + box_size * 0.4;
let my = by + box_size * 0.75;
let ex = bx + box_size * 0.8;
let ey = by + box_size * 0.25;
let mut builder = PathBuilder::new();
builder.move_to((cx, cy));
builder.line_to((mx, my));
builder.line_to((ex, ey));
let path = builder.detach();
canvas.draw_path(&path, &check);
}

if !form.caption.is_empty() {
let font = self.make_form_font((h * 0.6).clamp(8.0, 13.0));
let mut tp = Paint::default();
tp.set_anti_alias(true);
tp.set_color(fg_color);
let tx = bx + box_size + 4.0;
let ty = y + h / 2.0 + font.size() * 0.35;
canvas.draw_str(&form.caption, (tx, ty), &font, &tp);
}
}
FormType::RadioButton => {
let r = h.min(w).min(14.0) / 2.0;
let cx = x + 2.0 + r;
let cy = y + h / 2.0;

let mut fill = Paint::default();
fill.set_anti_alias(true);
fill.set_style(paint::Style::Fill);
fill.set_color(bg_color);
canvas.draw_circle((cx, cy), r, &fill);

let mut stroke = Paint::default();
stroke.set_anti_alias(true);
stroke.set_style(paint::Style::Stroke);
stroke.set_stroke_width(1.0);
stroke.set_color(border_color);
canvas.draw_circle((cx, cy), r, &stroke);

if form.value != 0 {
let mut dot = Paint::default();
dot.set_anti_alias(true);
dot.set_style(paint::Style::Fill);
dot.set_color(fg_color);
canvas.draw_circle((cx, cy), r * 0.5, &dot);
}

if !form.caption.is_empty() {
let font = self.make_form_font((h * 0.6).clamp(8.0, 13.0));
let mut tp = Paint::default();
tp.set_anti_alias(true);
tp.set_color(fg_color);
let tx = cx + r + 4.0;
let ty = y + h / 2.0 + font.size() * 0.35;
canvas.draw_str(&form.caption, (tx, ty), &font, &tp);
}
}
FormType::ComboBox => {
let mut fill = Paint::default();
fill.set_anti_alias(true);
fill.set_style(paint::Style::Fill);
fill.set_color(bg_color);
canvas.draw_rect(rect, &fill);

let mut stroke = Paint::default();
stroke.set_anti_alias(true);
stroke.set_style(paint::Style::Stroke);
stroke.set_stroke_width(1.0);
stroke.set_color(border_color);
canvas.draw_rect(rect, &stroke);

// 드롭다운 화살표 영역
let arrow_w = h.min(20.0);
let ax = x + w - arrow_w;
let arrow_rect = Rect::from_xywh(ax, y, arrow_w, h);
let mut abg = Paint::default();
abg.set_anti_alias(true);
abg.set_style(paint::Style::Fill);
abg.set_color(bg_color);
canvas.draw_rect(arrow_rect, &abg);
canvas.draw_line((ax, y), (ax, y + h), &stroke);

// 화살표 삼각형
let mut arrow = Paint::default();
arrow.set_anti_alias(true);
arrow.set_style(paint::Style::Fill);
arrow.set_color(Color::from_rgb(80, 80, 80));
let acx = ax + arrow_w / 2.0;
let acy = y + h / 2.0;
let as_ = (arrow_w * 0.25).min(5.0);
let mut builder = PathBuilder::new();
builder.move_to((acx - as_, acy - as_ * 0.5));
builder.line_to((acx + as_, acy - as_ * 0.5));
builder.line_to((acx, acy + as_ * 0.5));
builder.close();
let path = builder.detach();
canvas.draw_path(&path, &arrow);

if !form.text.is_empty() {
let font = self.make_form_font((h * 0.55).clamp(8.0, 13.0));
let mut tp = Paint::default();
tp.set_anti_alias(true);
tp.set_color(fg_color);
let tx = x + 4.0;
let ty = y + h / 2.0 + font.size() * 0.35;
canvas.draw_str(&form.text, (tx, ty), &font, &tp);
}
}
FormType::Edit => {
let mut fill = Paint::default();
fill.set_anti_alias(true);
fill.set_style(paint::Style::Fill);
fill.set_color(bg_color);
canvas.draw_rect(rect, &fill);

let mut stroke = Paint::default();
stroke.set_anti_alias(true);
stroke.set_style(paint::Style::Stroke);
stroke.set_stroke_width(1.0);
stroke.set_color(border_color);
canvas.draw_rect(rect, &stroke);

if !form.text.is_empty() {
let font = self.make_form_font((h * 0.55).clamp(8.0, 13.0));
let mut tp = Paint::default();
tp.set_anti_alias(true);
tp.set_color(fg_color);
let tx = x + 4.0;
let ty = y + h / 2.0 + font.size() * 0.35;
canvas.draw_str(&form.text, (tx, ty), &font, &tp);
}
}
}
}

fn parse_css_color(s: &str) -> Option<Color> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기서 draw_form_control 함수는 닫혔지만, 위에서 새로 연 impl SkiaLayerRenderer 블록이 아직 닫히지 않은 상태로 보입니다. 그래서 아래 parse_css_color/mod tests가 impl 내부로 들어가면서 native CI가 unclosed delimiter로 실패합니다. fn parse_css_color 앞에 }를 하나 더 추가해 impl 블록을 닫아야 할 것 같습니다.

let s = s.trim().trim_start_matches('#');
if s.len() != 6 { return None; }
let r = u8::from_str_radix(&s[0..2], 16).ok()?;
let g = u8::from_str_radix(&s[2..4], 16).ok()?;
let b = u8::from_str_radix(&s[4..6], 16).ok()?;
Some(Color::from_rgb(r, g, b))
}

fn colorref_to_skia(color: ColorRef, alpha_scale: f32) -> Color {
let b = ((color >> 16) & 0xFF) as u8;
let g = ((color >> 8) & 0xFF) as u8;
Expand Down
Loading