diff --git a/docs/index.html b/docs/index.html index 0cfe5f8..916d42c 100644 --- a/docs/index.html +++ b/docs/index.html @@ -213,8 +213,8 @@

YutilsAn ASS typeset utilities library

Splits lines of shape shape into shorter ones to fix to maximal line length of max_len, creating a new shape.
-outline_shape = shape.to_outline(shape, width_xy[, width_y])
-Converts shape shape from his filling to the stroke with horizontal width width_xy and vertical width width_y, returned as new shape. If width_y isn't defined, width_xy stands for both. +outline_shape = shape.to_outline(shape, width_xy[, width_y][, mode])
+Converts shape shape from his filling to the stroke with horizontal width width_xy, vertical width width_y and join type mode which can be "round"|"bevel"|"miter", returned as new shape. If width_y isn't defined, width_xy stands for both. Default join type is "round".
pixels = shape.to_pixels(shape)
diff --git a/src/Yutils.lua b/src/Yutils.lua index df6f8fd..a78d06d 100644 --- a/src/Yutils.lua +++ b/src/Yutils.lua @@ -19,7 +19,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ----------------------------------------------------------------------------------------------------------------- - Version: 26th October 2014, 19:10 (GMT+1) + Version: 4th November 2014, 04:00 (GMT+1) Yutils table @@ -60,7 +60,7 @@ glue(src_shape, dst_shape[, transform_callback]) -> string move(shape, x, y) -> string split(shape, max_len) -> string - to_outline(shape, width_xy[, width_y]) -> string + to_outline(shape, width_xy[, width_y][, mode]) -> string to_pixels(shape) -> table transform(shape, matrix) -> string ass @@ -113,6 +113,7 @@ local FP_PRECISION = 3 -- Floating point precision by numbers behind point (for shape points) local CURVE_TOLERANCE = 1 -- Angle in degree to define a curve as flat local MAX_CIRCUMFERENCE = 1.5 -- Circumference step size to create round edges out of lines +local MITER_LIMIT = 200 -- Maximal length of a miter join local SUPERSAMPLING = 8 -- Anti-aliasing precision for shape to pixels conversion local FONT_PRECISION = 64 -- Font scale for better precision output from native font system local LIBASS_FONTHACK = true -- Scale font data to fontsize? (no effect on windows) @@ -1591,12 +1592,14 @@ Yutils = { return table.concat(new_shape, " ") end, -- Converts shape to stroke version - to_outline = function(shape, width_xy, width_y) + to_outline = function(shape, width_xy, width_y, mode) -- Check arguments - if type(shape) ~= "string" or type(width_xy) ~= "number" or width_xy < 0 or not (width_y == nil or type(width_y) == "number" and width_y >= 0) then - error("shape and line width (general or horizontal and vertical) expected", 2) - elseif width_y and not (width_xy > 0 or width_y > 0) or width_xy == 0 then + if type(shape) ~= "string" or type(width_xy) ~= "number" or width_y ~= nil and type(width_y) ~= "number" or mode ~= nil and type(mode) ~= "string" then + error("shape, line width (general or horizontal and vertical) and optional mode expected", 2) + elseif width_y and (width_xy < 0 or width_y < 0 or not (width_xy > 0 or width_y > 0)) or width_xy <= 0 then error("one width must be >0", 2) + elseif mode and mode ~= "round" and mode ~= "bevel" and mode ~= "miter" then + error("valid mode expected", 2) end -- Line width values local width, xscale, yscale @@ -1694,25 +1697,67 @@ Yutils = { o_vec1_x, o_vec1_y = Yutils.math.stretch(o_vec1_x, o_vec1_y, 0, width) local o_vec2_x, o_vec2_y = Yutils.math.ortho(post_point[1]-point[1], post_point[2]-point[2], 0, 0, 0, 1) o_vec2_x, o_vec2_y = Yutils.math.stretch(o_vec2_x, o_vec2_y, 0, width) - -- Calculate degree & circumference between orthogonal vectors - local degree = Yutils.math.degree(o_vec1_x, o_vec1_y, 0, o_vec2_x, o_vec2_y, 0) - local circ = math.abs(math.rad(degree)) * width -- Add first edge point outline_n = outline_n + 1 outline[outline_n] = string.format("%s%s %s", - outline_n == 1 and "m " or outline_n == 2 and "l " or "", + outline_n == 1 and "m " or "", Yutils.math.round(point[1] + o_vec1_x * xscale, FP_PRECISION), Yutils.math.round(point[2] + o_vec1_y * yscale, FP_PRECISION)) - -- Round edge needed? - if circ > MAX_CIRCUMFERENCE then - local circ_rest = circ % MAX_CIRCUMFERENCE - for cur_circ = circ_rest > 0 and circ_rest or MAX_CIRCUMFERENCE, circ, MAX_CIRCUMFERENCE do - local curve_vec_x, curve_vec_y = rotate2d(o_vec1_x, o_vec1_y, cur_circ / circ * degree) + -- Create join by mode + if mode == "bevel" then + -- Nothing to add! + elseif mode == "miter" then + -- Add mid edge point(s) + local vec1_x, vec1_y, vec2_x, vec2_y = point[1]-pre_point[1], point[2]-pre_point[2], point[1]-post_point[1], point[2]-post_point[2] + local delta = vec1_x * vec2_y - vec2_x * vec1_y + if delta == 0 then -- Parallel vectors + vec1_x, vec1_y = Yutils.math.stretch(vec1_x, vec1_y, 0, MITER_LIMIT) + vec2_x, vec2_y = Yutils.math.stretch(vec2_x, vec2_y, 0, MITER_LIMIT) outline_n = outline_n + 1 - outline[outline_n] = string.format("%s%s %s", - outline_n == 1 and "m " or outline_n == 2 and "l " or "", - Yutils.math.round(point[1] + curve_vec_x * xscale, FP_PRECISION), Yutils.math.round(point[2] + curve_vec_y * yscale, FP_PRECISION)) + outline[outline_n] = string.format("%s%s %s %s %s", + outline_n == 2 and "l " or "", + Yutils.math.round(point[1] + (o_vec1_x + vec1_x) * xscale, FP_PRECISION), Yutils.math.round(point[2] + (o_vec1_y + vec1_y) * yscale, FP_PRECISION), + Yutils.math.round(point[1] + (o_vec2_x + vec2_x) * xscale, FP_PRECISION), Yutils.math.round(point[2] + (o_vec2_y + vec2_y) * yscale, FP_PRECISION)) + else -- Vectors intersect + local pre, post = (point[1] + o_vec1_x) * (point[2] + o_vec1_y + vec1_y) - (point[1] + o_vec1_x + vec1_x) * (point[2] + o_vec1_y), + (point[1] + o_vec2_x) * (point[2] + o_vec2_y + vec2_y) - (point[1] + o_vec2_x + vec2_x) * (point[2] + o_vec2_y) + local is_vec_x, is_vec_y = (pre * -vec2_x - post * -vec1_x) / delta - point[1], (pre * -vec2_y - post * -vec1_y) / delta - point[2] + local is_vec_len = Yutils.math.distance(is_vec_x, is_vec_y) + if is_vec_len > MITER_LIMIT then + local fix_scale = MITER_LIMIT / is_vec_len + outline_n = outline_n + 1 + outline[outline_n] = string.format("%s%s %s %s %s", + outline_n == 2 and "l " or "", + Yutils.math.round(point[1] + (o_vec1_x + (is_vec_x - o_vec1_x) * fix_scale) * xscale, FP_PRECISION), Yutils.math.round(point[2] + (o_vec1_y + (is_vec_y - o_vec1_y) * fix_scale) * yscale, FP_PRECISION), + Yutils.math.round(point[1] + (o_vec2_x + (is_vec_x - o_vec2_x) * fix_scale) * xscale, FP_PRECISION), Yutils.math.round(point[2] + (o_vec2_y + (is_vec_y - o_vec2_y) * fix_scale) * yscale, FP_PRECISION)) + else + outline_n = outline_n + 1 + outline[outline_n] = string.format("%s%s %s", + outline_n == 2 and "l " or "", + Yutils.math.round(point[1] + is_vec_x * xscale, FP_PRECISION), Yutils.math.round(point[2] + is_vec_y * yscale, FP_PRECISION)) + end + end + else -- not mode or mode == "round" + -- Calculate degree & circumference between orthogonal vectors + local degree = Yutils.math.degree(o_vec1_x, o_vec1_y, 0, o_vec2_x, o_vec2_y, 0) + local circ = math.abs(math.rad(degree)) * width + -- Join needed? + if circ > MAX_CIRCUMFERENCE then + -- Add curve edge points + local circ_rest = circ % MAX_CIRCUMFERENCE + for cur_circ = circ_rest > 0 and circ_rest or MAX_CIRCUMFERENCE, circ - MAX_CIRCUMFERENCE, MAX_CIRCUMFERENCE do + local curve_vec_x, curve_vec_y = rotate2d(o_vec1_x, o_vec1_y, cur_circ / circ * degree) + outline_n = outline_n + 1 + outline[outline_n] = string.format("%s%s %s", + outline_n == 2 and "l " or "", + Yutils.math.round(point[1] + curve_vec_x * xscale, FP_PRECISION), Yutils.math.round(point[2] + curve_vec_y * yscale, FP_PRECISION)) + end end end + -- Add end edge point + outline_n = outline_n + 1 + outline[outline_n] = string.format("%s%s %s", + outline_n == 2 and "l " or "", + Yutils.math.round(point[1] + o_vec2_x * xscale, FP_PRECISION), Yutils.math.round(point[2] + o_vec2_y * yscale, FP_PRECISION)) end -- Insert inner or outer outline to stroke shape stroke_shape_n = stroke_shape_n + 1 diff --git a/tests/shape.lua b/tests/shape.lua index d4201c8..8e9a08f 100644 --- a/tests/shape.lua +++ b/tests/shape.lua @@ -9,7 +9,7 @@ print("Flattened shape: " .. Yutils.shape.flatten(shape)) print("Moved shape: " .. Yutils.shape.move(shape, 5, -2)) print("Mirrored shape: " .. Yutils.shape.transform(shape, Yutils.math.create_matrix().rotate("y", 180))) print("Shape with splitted lines: " .. Yutils.shape.split(shape, 15)) -print("Shape outline: " .. Yutils.shape.to_outline(Yutils.shape.flatten(shape), 5.5, 10)) +print("Shape outline: " .. Yutils.shape.to_outline(Yutils.shape.flatten(shape), 5.5, 10, "miter")) print("Pixels:\n" .. Yutils.table.tostring(Yutils.shape.to_pixels("m -4.5 -4 l 0 -4 0 0"))) print( "Detected shapes:\n" ..